503 Commits

Author SHA1 Message Date
b040abcdf6 Draft for the Keythley Pulse System driver
Change-Id: Idd6f86acbaac178ab8031625b36ded1351de6a34
2024-08-26 16:49:07 +02:00
0def8f52e7 Comments
Change-Id: Ie7fe10f704aec62c19cae0bab16d43d55d911a36
2024-08-26 14:23:21 +02:00
c074d2cbba Changed temperature range
Change-Id: Ib2a0bc59b191d33e65420375385d8807754193a9
2024-08-26 14:17:52 +02:00
d2c9524497 Changed temperatures
Change-Id: I1411e425bd911b73cfcb459de50e7eea2cd15c51
2024-08-26 14:15:48 +02:00
1c55b7dbf1 Driver with comments
Change-Id: Ic2d35960de6b33e4d61ad1920d2416e2d5ed1ded
2024-08-20 13:51:00 +02:00
b63dca3b4e Added documentation
Change-Id: Id6e26a4c28fe080a55099cd54d0fa85c15946657
2024-08-19 17:21:40 +02:00
5f1e37a99e .
Change-Id: If177029157edbd6123e9f41883dbb6c639a68852
2024-08-13 15:49:10 +02:00
8c606150f5 Status and autorange implemented for AC resistane bridge
Change-Id: I8c94660c3b76cc78886e9e074b4ce8114fbb7f9e
2024-06-04 16:46:52 +02:00
efc1694408 Communication with the modules for the ac resistance bridge.
Change-Id: I1c144bf0bfc7c8c3090aa7cf3f32f1d4d046dca7
2024-05-21 17:30:11 +02:00
cba15e0237 Communication with modules for a resistance bridge
Change-Id: Ia8622cd3620dff68566768411a05f9cf3618771a
2024-05-21 17:28:49 +02:00
41ed82e72e Driver and cfg file for ac resistance bridge
Change-Id: I77b2294b57315fcf7d94996a2a68fcac72866710
2024-05-06 11:57:50 +02:00
95ca85fd9c Frappy driver for a HP multimeter
Change-Id: I2e9dc5131ea9a7317d69c42dd49388216e77f72f
2024-04-23 17:20:55 +02:00
4f9372066a New driver for AC Resistance Bridge (with SIM921 modules)
Change-Id: I2c2da421453af0f41703805092423f9b02d1f9b4
2024-04-23 17:14:36 +02:00
be779b118b Haake driver
Change-Id: Ib6aec31ada835ebfb2f8a06b119e3b8189f9f51e
2024-01-31 11:10:41 +01:00
feb0566e87 Driver for HP multimeter
Change-Id: I45f70758331680fb7b044d28bf0dc50781d912d3
2024-01-31 11:10:40 +01:00
8b40e91578 cosmetic change in logdif.py
Change-Id: Ic9023d6d37067e527801cb7d092d1c8214f603f6
2024-01-29 16:02:25 +01:00
eeb666c5dd synced most of wip to mlz
Change-Id: Ifc5eb0d8ccf693535ab474553759f5622b3a3c8f
2024-01-29 16:00:59 +01:00
fac29af0ad adopt missing changes while cherry-picking from mlz
Change-Id: Icda4d581e8f0ebd22fc22f2661965bf98a821a34
2024-01-29 16:00:59 +01:00
d55ee42612 remove more coding cookies
mainly from frappy_psi

Change-Id: I192811459aebe97f3076888cd31a308a51e6aa49
2024-01-29 16:00:44 +01:00
75cfffb548 all: remove coding cookies
Change-Id: I53a4d79c3ebc50b8aed43a5ef1fa6538f8059a47
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/32251
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
Reviewed-by: Alexander Zaft <a.zaft@fz-juelich.de>
2024-01-29 16:00:08 +01:00
7d02b47c7a [deb] Release v0.18.1 2024-01-29 15:59:41 +01:00
ecfc0b2ab8 mlz: entangle fix limit check
Change-Id: Ib430262057026054ac71053d25dfda340b48227a
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/32921
Reviewed-by: Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
Tested-by: Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
2024-01-29 15:58:59 +01:00
04402e72b6 mlz: Zapf fix unit handling and small errors
Change-Id: Iaa5ed175582d8399cc0c69ba72c3ab8e6e51ecf6
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/32920
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Tested-by: Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
Reviewed-by: Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
2024-01-29 15:58:59 +01:00
61b65220e1 [deb] Release v0.18.0 2024-01-29 15:58:59 +01:00
67030985d6 bug fix in frappy.io.BytesIO.checkHWIdent
missing f for f string

Change-Id: Ie67384e5b7e514728041a72bd08c850abb31639e
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/32786
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Alexander Zaft <a.zaft@fz-juelich.de>
2024-01-29 15:58:59 +01:00
ca2ca5d7cb remove py35 compatibility code
as f-strings are heavily used now, compatibility to py35
can be removed

Change-Id: I1ae4912ad4cbde8419b74845217943bd061053f3
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/32754
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-01-29 15:58:59 +01:00
ba3c64ddac fix playground after change 32249
as modules are now stored on secnode instead of dispatcher

Change-Id: Iccda3d97269693a893c06a4e094a9c1dbcf7df0b
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/32746
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-01-29 15:58:59 +01:00
06bcf94fa2 frappy.secnode: fix strange error message
when get_module_instance is called a second time after
it failed, the 'cls' element in opts is missing:

move opts dict copy from create_modules to get_module_instance

Change-Id: Ie046f133a8fdbbb1c39643ca16dc5447a9d2d065
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/32745
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>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
2024-01-29 15:58:59 +01:00
f6f433e6b2 modify arguments of Dispatcher.announce_update
- 'pname' argument is not needed
- change 'modulename' argument to 'moduleobj'
  (needed for further change)

Change-Id: Ib21f8ad06d9b2be4005ff3513088a85e29785c94
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/32744
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-01-29 15:58:59 +01:00
2a165c5013 fix missing import in change message
Transported values in a change must be converted first.
As this is only relevant for the exotic "scaled" and "blob"
datatypes, this was not detected yet.

- add tests
- suppress warning PytestUnhandledThreadExceptionWarning in tests
+ change import_value methods to raise no other exceptions than
  WrongTypeError and RangeError
+ simplify Command.do: as import_value already raises the
  appropriate error, no more try/except is needed

Change-Id: I299e511468dc0fcecff4c20cf8a917da38b70786
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/32743
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
Reviewed-by: Alexander Zaft <a.zaft@fz-juelich.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
2024-01-29 15:58:56 +01:00
8e70b97ef7 core: better error on export of internal type
more descriptive error when trying to export OrType, NoneOr, ValueType
and DataTypeType

Change-Id: If13815e9d2b177042b24a1bb62b1ad1d1d88b502
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/32737
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Alexander Zaft <a.zaft@fz-juelich.de>
2024-01-29 15:58:48 +01:00
e1cda7d263 fix missing import in change message
Transported values in a change must be converted first.
As this is only relevant for the exotic "scaled" and "blob"
datatypes, this was not detected yet.

- add tests
- suppress warning PytestUnhandledThreadExceptionWarning in tests
+ change import_value methods to raise no other exceptions than
  WrongTypeError and RangeError
+ simplify Command.do: as import_value already raises the
  appropriate error, no more try/except is needed

Change-Id: I299e511468dc0fcecff4c20cf8a917da38b70786
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/32743
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
Reviewed-by: Alexander Zaft <a.zaft@fz-juelich.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
2024-01-29 15:58:43 +01:00
959a09957e core: better error on export of internal type
more descriptive error when trying to export OrType, NoneOr, ValueType
and DataTypeType

Change-Id: If13815e9d2b177042b24a1bb62b1ad1d1d88b502
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/32737
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Alexander Zaft <a.zaft@fz-juelich.de>
2024-01-29 15:58:43 +01:00
f68886a4ed frappy_psi.sea: workaround for bug in sea
hdb path should not contain duble slash. replace double slash
by single slash

Change-Id: Ia2ce3be9a75d68fcc7efe3eb3dbd19a7907a73ff
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/32705
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
2024-01-29 15:57:55 +01:00
eeea754181 core: better command handling
* check argument of do
* automatically set optional struct members from function signature

Change-Id: I95684f1826c1318ea92fad2bd4c9681d85ea72f5
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/32501
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Alexander Zaft <a.zaft@fz-juelich.de>
2024-01-29 15:57:25 +01:00
ae7bf3ce96 datatypes: fix optional struct export
Change-Id: Ia2758dfba75f36a91bf1676e8ead555cec3ead53
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/32500
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
Reviewed-by: Alexander Zaft <a.zaft@fz-juelich.de>
2024-01-29 15:57:25 +01:00
affe3c161c mlz: handle unconfigured abslimits
- if there are no abslimits configured, get them from the hardware.
- check if the ranges are compatible

Change-Id: If72e31a56c299cb628ed8ff66d4340a87d4bd1d4
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/32625
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Alexander Zaft <a.zaft@fz-juelich.de>
2024-01-29 15:57:25 +01:00
5b6a9de96e core: formatting and update server docstring
Change-Id: Ic0dd4c5239f27679c89f6b3742b9c5f8b71f33f6
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/32514
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Alexander Zaft <a.zaft@fz-juelich.de>
2024-01-29 15:57:25 +01:00
34793f8bd0 core: allow multiple interfaces
Change-Id: Ib8c0baef85a6dd69cddafe1c4973e42136d1588b
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/32489
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
Reviewed-by: Alexander Zaft <a.zaft@fz-juelich.de>
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
2024-01-29 15:57:25 +01:00
7333ccd7a6 implement pfeiffer TPG vacuum reading
this is an example where StringIO.communicate has to be extended

Change-Id: Iff6bb426ee7960904993574531de84793152e21d
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/32385
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-01-29 15:57:25 +01:00
2d42eab5fa add StringIO.writeline, improve StringIO.multicomm
- StringIO.writeline sends a command and does not expect a reply
- StringIO.multicomm and BytesIO.multicomm is improved in order
  to insert individual delays in between lines and individual
  noreply flags

+ fix a bug in tutorial_t_control
+ improve readability of frappy.lib.classdoc.indent_description

Change-Id: I9dea113e19147684ec41aca5267a79816bbf202c
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/32267
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>
2024-01-29 15:57:25 +01:00
c88a71985a doc: drop latex support, add pdf support
latexpdf fails with error message "Too deply nested".
We want to avoid reducing the nesting level of doc strings
in frappy.lib.classdoc (less nice output) or a level of
nesting in method doc strings.

- latex removed from Jenkinsfile
- added support for rst2pdf

Change-Id: Ieb3355ef506e636e7e43a726c68327e3b1154469
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/32406
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-01-29 15:56:38 +01:00
ae2a731161 mlz/demo: move old examples to Attached
change very early version of module attachments in GarfieldMagnet and
MagnetigField to use Attached

Change-Id: I616ad17bc72cd93d86e1b3e3609543cfe90edcd8
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/32250
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
Reviewed-by: Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
2024-01-29 15:56:23 +01:00
ec664d9268 core: move module handling out of dispatcher
Split module handling code from the dispatcher.
The new class for managing Modules is called SecNode.

* change logging to no longer need a reference to modobj
* modules get a reference to the secnode obj instead of the
  dispatcher
* intermediate usage fixes for frappy_psi/sea

Change-Id: Ifee4bb47aa7a4508bb4a47c9a5873b7e2d5faf67
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/32249
Reviewed-by: Alexander Zaft <a.zaft@fz-juelich.de>
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
2024-01-29 15:56:19 +01:00
4ca7bf0a7d logdif.py: only one commit needs to be new enough
+ fixe intendation

Change-Id: I4b0c393767925532e1f105e80a215839a02214af
2024-01-29 15:55:52 +01:00
715fcf4d36 added logdif.py
a tool the compare commits in branches

Change-Id: I503941b76bb567ea4c3d33b986406a910154fda6
2024-01-29 11:42:43 +01:00
a429852c80 adopt frappy_psi and frappy changes from wip
Change-Id: I4e6732e755398d88b73007fb53b758039c5d4483
2024-01-29 10:56:58 +01:00
3eb5f90ce6 remove unneeded cfg/varioxb_cfg.py
Change-Id: Id731b4462e6bd91de66ee271e916aec044374fce
2024-01-29 10:47:54 +01:00
f1115c937b add cfg files from wip
Change-Id: I71b647c269c10eac7241970377d6f812636d082f
2024-01-29 10:45:42 +01:00
91d0f2c635 remove unneeded config files
Change-Id: I0a6a2d1471172ed2604265454b322a9f11f8d266
2024-01-29 10:41:25 +01:00
614a49f3e3 adopt sea amd cfg changes from wip
Change-Id: I83b82ece53e0cb3394551c122561a042499527d8
2024-01-29 10:33:47 +01:00
1e56f2f39e frappy_psi/SR.py: move soft auto range from read_value to doPoll
Change-Id: Ia94e081eaa4c28b1f436227635d7c5beb883792b
2024-01-29 10:27:22 +01:00
82e9b599ad sea: make sure status is error when reading main value failed
+ cosmetic changes using status codes

Change-Id: Ice49a6d5494d2e2cba76d138f11dffeba9aa349d
2023-12-11 08:24:11 +01:00
c4781d3d70 Autogain function for SR830 lock-in driver
Change-Id: If07ec9182e5153e1237b9818ce555162f54e0ae5
2023-11-20 13:20:28 +01:00
c0b928f2f6 Driver for ThermoHaake Phoenix P1 Circulator
Change-Id: I0573eeac2e40b4715072661c819701186733bf94
2023-11-07 13:26:16 +01:00
73bb0cff1e For the lockin830 get_par/set_par are implemented.
Change-Id: I5b6707a07d936d24528173a2edae49a148081ff9
2023-10-31 17:26:35 +01:00
6454e5f96e frappy_psi/thermofisher: version through gerrit
Change-Id: I6999e84d1c5efd0625c6df89e97dad46e5a8cd59
2023-10-16 17:49:36 +02:00
3f53823c07 newset version of oksanas drivers
Change-Id: Ia6d8b727e48e96a14b75feeef5d3e6c002cb82a0
2023-10-16 17:49:36 +02:00
fe60a8ebd8 frappy_psi.phytron: implement limit switches 2023-10-16 17:49:36 +02:00
fb0a689786 ma6: make ts drivable 2023-10-16 17:49:36 +02:00
2ee12a62f0 fix ma15 sea config 2023-10-16 17:49:36 +02:00
de970ece3a configs for sample heat stick 2023-10-16 17:49:36 +02:00
59810aa748 proxy: fix command wrapper
bugfix: return only value of execCommand result, not qualifiers
Change-Id: Iff14779050daa9886e9f7d0396317c5a41695cd1
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/32235
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
2023-10-16 17:49:36 +02:00
d2e7ce7242 fix simulation
+ some fixed in sim_uniax

Change-Id: Ia8703ed988aa904bb2694339f0d3175b28fcb33e
2023-10-16 17:49:36 +02:00
72fbed289d more cfg file fixes
Change-Id: I0ba86cd17bb07f480cac6f20994ee854c6e811ae
2023-10-16 17:49:36 +02:00
60c6553ee7 cleanup cfg files 2023-10-16 17:49:36 +02:00
cf10590245 improvements for flame
- frappy_psi.channelswitcher: use isBusy instead of checking value and target
- frappy_psi.ls372: remove underflow mechanism
- frappy_psi.parmod.SwitchDriv: switch the controlled module also when not buys
2023-10-16 17:49:36 +02:00
44c59bd818 flamedil as of 2023-07-04 2023-10-16 17:49:36 +02:00
c355999e85 flamedil as of 2023-07-03 2023-10-16 17:49:36 +02:00
4f0daf3424 flame sample combined T 2023-10-16 17:49:36 +02:00
be7c9eec8c frappy_psi.ls372: add TemperatureSensor and TemperatureLoop 2023-10-16 17:49:36 +02:00
ef5f4cd2f3 frappy_psi.cryoltd: fixes after frappy upgrade 2023-10-16 17:49:36 +02:00
1da16f12c4 frappy_psi.triton: try to fix channel selection before condense action 2023-10-16 17:49:36 +02:00
2658dd8090 frappy_psi.sea: small fixes
- changes in return value of frappy_config command in sea
- do not store sea manager

Change-Id: I5bc1d9a281ad2285b90d3649b4c702a3501d451d
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/32166
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
2023-10-16 17:49:31 +02:00
fa5d5654f8 phytron.py: improve status
better analysis of hardware status code

Change-Id: I667b443649db43ff3e572e0a50685aabc9ba2ca2
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/32165
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
2023-10-16 17:49:31 +02:00
bf8d5101b0 add ill2 2023-10-16 17:49:30 +02:00
31b5928725 fix fs config 2023-10-16 17:49:30 +02:00
110855200a more consistent ori1 stick json file 2023-10-16 17:49:30 +02:00
21f9921269 improve client shutdown time
in SecopClient.disconnect joinng the reconnect thread may take
up to 10 s, because of the time.sleep(10) call in the reconnect
thread.

change the _shutdown attribute from bool to an Event, and
use Event.wait instead of time.sleep

Change-Id: Icea6a14ad73df0b3d26ef45806f4c05e6bf18492
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/32137
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
2023-10-16 17:49:30 +02:00
bce88ff343 change FloatRange arguments minval/maxval to min/max
in the previous version FloatRange(max=100) was neither working
properly nor complaining, because the maxval=None default was
overriding the value for max.

possible fixes:
  - raise an error when min/max used as argument (confusing for
    the programmer, as it is a property)
  - allow both versions minval/maxval and min/max (more code)
  - use min/max and a pylint directive here (the only thing to
    take care is not to use the min/max builtin in __init__)

this change uses the last option for the fix

Change-Id: Iff0e0c4d0d7b165003bdeffa67a93a1cd7f29eea
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/31982
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
2023-10-16 17:49:30 +02:00
5359f0351f fix frappy_demo.lakeshore
reading back the target does not work properly, because
  a) the readback value might be delayed
  b) there is no command to read back the target, SETP?1
     is returning the working setpoint, which might be distinct
     in case of a ramp

Change-Id: I0da2dbfc1a8ddbecbae6d0456ff64e008bc56336
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/31983
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
2023-10-16 17:49:30 +02:00
ab2e001093 psi: improve sea interface
- get return value from teh frappy-config script in order
  to detect failures
- call config_check not more than once within 1 sec

Change-Id: Ibe42e846521206463f2761d452aea7e558a36854
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/32139
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
2023-10-16 17:49:30 +02:00
ff0e77a166 fix missing .poll attribute in simulation
using super() in SimBase.__new__ fixes the problem

Change-Id: I18d0ba6ac476c2edb0d973090bcb09508a983d6a
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/32136
Reviewed-by: Alexander Zaft <a.zaft@fz-juelich.de>
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
2023-10-16 17:49:30 +02:00
195a48f366 further fixes after change 31470
- get_module is to be called when io is autocreated
- register_module is missing in playground

Change-Id: I28884575b71320667107c494473b0fc5d4363a50
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/32123
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>
2023-10-16 17:49:30 +02:00
2110f8d83b add parmod.Par
the reasonly class frappy_psi.parmod.Par represents a parameter
or a component of a tuple parameter

Change-Id: I47208c9d7a6fc377cd56b82cc6a9e8cdb433fe8e
2023-10-16 17:49:30 +02:00
4c6c7c7950 improve shutdown time
on shutdown, time.sleep(10) is blocking the reconnect thread.
change the _shutdown attribute from bool to an Event, and
use Event.wait instead of time.sleep

Change-Id: Icea6a14ad73df0b3d26ef45806f4c05e6bf18492
2023-10-16 17:49:30 +02:00
7d7972b73f fix frappy/playground.py after change 31470
assumptions about dispatcher in playground.py are no longer
valid.

- let Dispatcher class in playground inherit from real dispatcher
+ improve log messages

Change-Id: I2a9a9d532dabadc590543660c445c021dd2f2891
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/31967
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>
2023-10-16 17:49:30 +02:00
fc40f5e0c9 ma10: improve sea cfg 2023-10-16 17:49:30 +02:00
a19b085c30 hvolt_short stick: make hcp writable 2023-10-16 17:49:30 +02:00
09772eb746 stickmotor addon: add backlash -1 2023-10-16 17:49:30 +02:00
c249b1648d frappy_psi.phytron: further improvements
unfortunaely, sometimes communication errors happen.
workaround: try several times reading the status

Change-Id: I2788c6c9b4145246cdd51c31b246abffee60f93b
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/32032
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
2023-10-16 17:49:30 +02:00
480913dabe add zapf to requirements-dev
Change-Id: I6dddd8d4c590253f1039b89edae561fa90b40811
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/31725
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Alexander Zaft <a.zaft@fz-juelich.de>
2023-10-16 17:49:30 +02:00
c2a080360d Revert "add zapf to requirements-dev.txt"
This reverts commit e67a46cd015c0a1a32d5a4f114b963dd17a7c266.

Reason for revert: required version available from pypi

Change-Id: Ib4f8b0cf62da58e84545511c7521ea93b7ff1342
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/31724
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Alexander Zaft <a.zaft@fz-juelich.de>
2023-10-16 17:49:30 +02:00
923da8ca15 interactive client: improve keyboard interrupt
- when driving a module with <module>(<target>),
  keyboard interrupt should send stop()

- make sure keyboard interrupt does not only stop
  the current driving, but also skips other code
  on the same command line

Change-Id: Ib4d2c4111dc0f23bf07385065766fb9b4a611454
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/31926
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
2023-10-16 17:49:30 +02:00
7a2e764262 frappy_mlz: Add Zapf PLC
adds a zapf-based PLC connection scanner.

Change-Id: Icc0ded7e7a8cc5a83d7527d9b26b37c49e9b8674
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/31471
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
Reviewed-by: Alexander Zaft <a.zaft@fz-juelich.de>
2023-10-16 17:49:30 +02:00
d726fd9fa0 add zapf to requirements-dev.txt
Change-Id: Ia4de696051cee1e00676e777b7dd2c0a90a0c504
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/31719
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Alexander Zaft <a.zaft@fz-juelich.de>
2023-10-16 17:49:30 +02:00
267acf644c core: do not call register_module on error
Dispatcher.get_module_instance returns None on failure.
If that is the case, the dispatcher should not try to register the
None value as a module.

Change-Id: Ie33b8debc2a829d480d56cafc1eb0ab610181d67
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/31713
Reviewed-by: Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
Reviewed-by: Alexander Zaft <a.zaft@fz-juelich.de>
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
2023-10-16 17:49:30 +02:00
0bc8accaaa frappy_mlz: fix one-off error in barcode reader
cut of one byte too much in barcode decode

Change-Id: I5f1f8475f197b13af836d685dc6da5a9ee824dc2
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/31728
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Alexander Zaft <a.zaft@fz-juelich.de>
2023-10-16 17:49:30 +02:00
0351ebd98d dispatcher: change logging calls to debug
Some logging calls should not have landed as log.info in the dynamic
modules patch. This fixes that.

Change-Id: I666fc7c9b5c65ddbed1c26ea456becce7870e744
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/31707
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Alexander Zaft <a.zaft@fz-juelich.de>
2023-10-16 17:49:30 +02:00
3a235f6380 frappy_mlz: Zebra fixes after basic test
Some fixes after the device was tested with socat ptys and NICOS.

Change-Id: I3e9dba2be2547d493c435d1da9844c932a2df4e6
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/31662
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Georg Brandl <g.brandl@fz-juelich.de>
Reviewed-by: Alexander Zaft <a.zaft@fz-juelich.de>
2023-10-16 17:49:30 +02:00
500c5f8c5d mlz: Add Zebra Barcode Reader
Adds a Barcode reader device (for now, only for ANTARES). Not yet
tested with real hardware.

Change-Id: I25f097466be89d152f47b9d05ece8f562e4b34d6
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/31412
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>
2023-10-16 17:49:30 +02:00
6b8e6498fa Revert "revert commits done before MZ holidays"
This reverts commit d2885bdd72.
2023-10-16 17:49:30 +02:00
4fc6ef52da frappy_psi.phytron: stop motor before restart
restarting the phytron motor without prior stop leads
to funny behaviour.

- send stop before restart
- stop motor when moving but status not busy
- restart when motor drives the wrong way

+ better status text when stopping

Change-Id: I82cd59297b3c79a354a4eeb5ba03fc65bedf755f
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/31929
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
2023-10-16 17:49:30 +02:00
fcb5052565 add hvolt_short 2023-10-16 17:49:30 +02:00
acc7580dc6 frappy/protocol/interface/tcp.py: use SECoP_DEFAULT_PORT
import SECoP_DEFAULT_PORT instead of defining DEF_PORT

Change-Id: I02ee420d200f90b61f8c79e1cb5ee3e0913955e9
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/31913
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>
2023-10-16 17:49:19 +02:00
4f28e5555c improve error message on client when host/port is bad
for host name without port, None was used for the port
leading to a confusing error message

- do not call parse_host_port with None as defaultport argument
- improve error message when connection fails

+ fix an error in last line of parse_ipv6_host_and_port
+ fix some issues breaking PEP 8 rules

Change-Id: I437360be96449c164f0080e3c60f1685825d4780
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/31911
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>
2023-10-16 17:49:19 +02:00
ec2bc57ade fix haake_cfg 2023-10-16 17:49:19 +02:00
7142cee0db fix ma7.config.json 2023-10-16 17:49:19 +02:00
a3d7520da9 add ori7 2023-10-16 17:49:19 +02:00
dmc
9d5ed822e8 insert pressure reading into ccrpe_cfg 2023-10-16 17:49:19 +02:00
b6c2cf8d3a MA7: add unit=T to mf 2023-10-16 17:49:19 +02:00
a26ae0da4e sMA6 encoder mode to CHECK
after Oksana experienced that it works
2023-10-16 17:49:19 +02:00
a7cbb35455 frappy.client.interactive: bug fixes
- correct behaviour with the following untypical message sequence:
  - send change target
  - receive status idle
  - receive status busy
  - receive changed target

- add 'exception' to Logger

Change-Id: I614b2a2c2e09ef1b43544838ccb2fa43357dd50d
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/31632
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>
2023-10-16 17:49:19 +02:00
e866ae7034 frappy_psi.sea: further bug fixes
- in SEA, it is not guaranteed that the is_running state is set
  before the run command returns. as a consequence, we have to
  wait in SeaDrivable.write_target for is_running being set
- syncio has always to be reconnected after asynio

Change-Id: Ia46cff11de86868ce0627faaf6f776282bd7a8f4
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/31631
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>
2023-10-16 17:49:19 +02:00
f35dae5893 ma7: sea confg: make ta/tb visible 2023-10-16 17:49:19 +02:00
2fc9eccfd5 frappy_psi.sea: avoid multiple connections
the _connect was sometimes started in parallel from
startModules and the first call to doPoll.
remove the first one, and protect the second one
with a lock

Change-Id: I079439e150efd5d005130cef475f6326f933ecbd
2023-10-16 17:49:19 +02:00
ca57f46ed2 frappy.io: make error reporting consistent
- fix mechanism to avoid multiple error messages in log files

Change-Id: I688071f9b06da1a81eb12d63adb549042171c7c8
2023-10-16 17:49:19 +02:00
1776d7b4b6 consmetic changes to ma6_sample_heat_cfg.py 2023-10-16 17:49:19 +02:00
34f46ab0b0 MA6: set om.encoder_mode to 'NO' 2023-10-16 17:49:19 +02:00
98a73d7882 add special configurations m6/ma7 sampleheat 2023-10-16 17:49:19 +02:00
e7148a30cb MA7/MA11: make ts drivable 2023-10-16 17:49:19 +02:00
da948468b4 update haake + eurotherm cfg 2023-10-16 17:49:19 +02:00
75df438d69 disable encoder for MA11 stick rotation 2023-10-16 17:49:19 +02:00
10fe42babf add FW (old power rack, via SEA) 2023-10-16 17:49:19 +02:00
8124ed3294 revert commits done before MZ holidays
they are all not neccessary for SINQ SE operation

Change-Id: Ic9adcccf685752ab90bb6b86005ac8e04b302855
2023-10-16 17:49:19 +02:00
aa7910c28c update to gerrit version
Change-Id: Ifdaa28dd961a529cd9197c4c3639744f108b0a6a
2023-10-16 17:49:19 +02:00
ff6a98af92 frappy_psi.thermofisher: add version through gerrit
Change-Id: I4b89d6ec803ad64c41720bc62493d2e4027df50e
2023-10-16 17:49:19 +02:00
0647b34bb8 add StructParam
adds a generic solution for creating parameters with struct datatype
with their members linked to individual parameters.

main use case: ctrlpars

read_*/write_* methods are either created for the main (structed)
parameter based on the corresponding methods of the individual
parameters or the methods for the individual parameters are created
based on the methods of the main parameter

+ disable pylint use-dict-literal

Change-Id: I7f1d9fb3d3b2226b548c2999bbfebe2ba5ac285e
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/31405
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
2023-10-16 17:49:19 +02:00
271bb27699 server: add option to dynamically create devices
add module which scans a connection and registers new devices depending
on the answer.
* change module initialization to demand-based
* move code from server to dispatcher
- remove intermediate step in Attached __get__

TODO:
  factor out dispatcher (regards to playground)
  discuss factoring out of module creation code from server AND
  dispatcher

Change-Id: I7af959b99a84c291c526aac067a4e2bf3cd741d4
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/31470
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>
Reviewed-by: Alexander Zaft <a.zaft@fz-juelich.de>
2023-10-16 17:49:19 +02:00
2cbe3f19b4 pylint: disable use-dict-literal
sometimes it is nicer to use dict(...) instead of {}
an objections against removing this check from pylint?

Change-Id: Ib08d3016b7ec3512111021a82685253cdcd42916
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/31505
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>
2023-10-16 17:49:19 +02:00
228902a6ee fixes in ori3 and dil5 config 2023-10-16 17:49:19 +02:00
bee591c861 frappy_psi.sea: try to reconnect on failure
both .asynio and .syncio connection should be tried to reopen.
(fix from mlz gerrit)

Change-Id: I0da5bd9927865a1c55afb93a7a5b76c44fc8750e
2023-10-16 17:49:19 +02:00
8e7e70c50e frappy_psi.sea: auto connect
on both .ssynio and /syncio try to reconnect after failure
2023-10-16 17:49:19 +02:00
e36ff9bc41 add ma15 cfg 2023-10-16 17:47:29 +02:00
e02c9b1378 fix systemd bug
Change-Id: I8a3f1eddba9525589757d4612a5060267ea0c5db
2023-10-16 17:47:29 +02:00
5a19103d0e frappy_psi.thermofisher improvements
- merge Loop with Sensor
- make convergence work

Change-Id: Iba0cafc524ada6d490b7a5c30f4127e77fd163f3
2023-10-16 17:45:45 +02:00
e201e7dce9 up to date with develop/mlz
Change-Id: I5ea71bc99a2f0dffc3dbe37e1119eb188ef8a3f0
2023-10-16 17:42:53 +02:00
532e4a7ab5 update 2023-05-30 from gitmlz
Change-Id: I0b1eb2941692fde5c9d98f107fc38315625dcfdb
2023-10-16 17:39:43 +02:00
c9ccaa484a SR830: moved dicts out of class
Change-Id: If056b1bf4e81c3b609ded087dff2b40c7119903f
2023-10-16 13:43:57 +02:00
a5dfc82b95 Changed write_range, write_tc methods
Change-Id: I335f97bd54deaccf0552b27deb3a7dfe73074e4c
2023-10-09 14:28:16 +02:00
e22da0db67 Changed driver for lock-in 65
Change-Id: Ic452f2309e54dd583a1ff796e82a12469357830d
2023-09-28 09:15:25 +02:00
e89bc07759 New driver for lock-in amplifier SR830
Change-Id: I45c5a06460f4b84cade0eae53188b058510c4473
2023-09-28 09:14:22 +02:00
e405783716 Removed status
Change-Id: I276fe69a4ca46f536be022d9604377378e41f26c
2023-07-12 15:19:16 +02:00
dc59906c8a Added tc for the Ametek 7265
Change-Id: Ifd8e55b2da14cb41391f72787cd726951192ec95
2023-07-12 15:13:35 +02:00
161fb7b2ca Changed configuration file for Ametek lockin 7270
Change-Id: I7a1f3382df1dcc168cf2997c92ea7696f17a779d
2023-07-12 15:11:19 +02:00
04940b1a0b write_range, write_tc, string_to_value method
Change-Id: I6f81db72e852d2670e0a774a621c8382680bb93a
2023-06-28 13:03:21 +02:00
6f6f07b0f9 The write method is changed
Change-Id: I5ffeecfeb43804d1f443e500808d9ff83c507d18
2023-06-27 09:56:28 +02:00
a44d232dea remove unused cfg/sea/*.py
Change-Id: Ide289a30f558386d08ce4cdeb439f7438c072231
2023-06-20 11:07:23 +02:00
32b1d6412d camea filter addon
Change-Id: I1d80aa3bfc4e441ad8a69930b81d6cc25cee9511
2023-06-20 11:03:37 +02:00
87830a1473 branch develop: recent changes from branch wip
Change-Id: I2e1173423f2aa164a8a7158921b354c2aff1ab2c
2023-06-20 10:59:18 +02:00
fcca3801a4 branch develop: cfgfiles cfg/main for branch wip
Change-Id: Iece67aedc45300b920378a83cfebf5f617c1bad3
2023-06-20 10:55:12 +02:00
00b57e3d2c GUI bugfix: use isChecked instead of checkState in BoolInput
Change-Id: I68153543d8f3424a70da5b3999e68f3e91edb2dc
2023-06-15 10:23:07 +02:00
720d010c47 frappy_psi.thermofisher improvements
- merge Loop with Sensor
- make convergence work

Change-Id: Iba0cafc524ada6d490b7a5c30f4127e77fd163f3
2023-06-05 09:52:18 +02:00
9e52665b88 frappy_psi.convergence: improvments
- merge_status
- empty string instead of 'approaching'
- dif <= tol

Change-Id: I6f10875f7ef5d2109c13d7448ede114b8e30d86e
2023-06-05 09:52:18 +02:00
a529cb9ab1 frappy.client: missing exception method in dummy logger
Change-Id: Ie3a574c3060f2ac6833ff44e8074a19db6ea2f0b
2023-06-05 09:52:18 +02:00
33fb90af05 fixed autorange, vmode, amp, irange, freq
Change-Id: Idae23ad1b5716375484acf0410843141e2e45d24
2023-06-01 17:03:02 +02:00
da122ad961 edited lockin (SR)
Change-Id: I66d95144d61c62a2396933c2f9a7ce6e05917fe4
2023-05-31 17:25:59 +02:00
11d1ad546f up to date with mlz
Change-Id: I205ccc9847771ebe5622a45792a4dbe8d8e02b82
2023-05-31 14:29:25 +02:00
37d28c9f35 update 2023-05-30 from gitmlz
Change-Id: I0b1eb2941692fde5c9d98f107fc38315625dcfdb
2023-05-31 14:17:21 +02:00
dc0cc590ed Merge branch 'wip' into develop
Change-Id: Ib5084b8750b31523819c688f4954c52cef4d4a0c
2023-05-31 14:06:05 +02:00
0771cb6899 working on lockin (SR)
Change-Id: Ia8dc71bf1029feaa66614ade0f8e9e99358f142e
2023-05-31 13:39:09 +02:00
564620e9e3 Test for lockin driver
Change-Id: I9db745088efdef182154b71e019a0f6f83644278
2023-05-31 13:39:09 +02:00
e1b30bf37e merge with wip branch 2023-05-17 17:00:06 +02:00
439c9d34c1 add test cfg for lockin
Change-Id: I7985202df5848b30bdf55f62453321932422ab01
2023-05-17 16:04:09 +02:00
ac90999bdd add Thermofisher to doc
Change-Id: I90c3b8d07b20d0f7b90db9d30e9f3b835fe75937
2023-05-17 16:03:55 +02:00
85dd905cfd add SR lockin
Change-Id: I4807ead7ceefcf0e266f7d215ef2898d54da9f53
2023-05-17 16:03:18 +02:00
04b9bed7c9 thermofisher updates
Change-Id: I6e7103e87cb3c1e69ef4d0e16d06e3d0fc3729ea
2023-05-17 16:02:41 +02:00
1171245704 add playground
+ fix SR_7270 communicator

Change-Id: If9604f9a6fe59ca3de3bbdbecf1b5053fce0573e
2023-04-26 13:54:53 +02:00
ab1fdbae16 draft versions of lock-in and signal generator
Change-Id: Icf4e3968efc3028af0de699a68a98a81e04ac9b6
2023-04-26 11:33:49 +02:00
2d628e151c finished thermofisher
but: convergence does not work yet properly
Change-Id: I834f8368730c347ba9f08a03eceae1a60fc66f90
2023-04-26 10:23:48 +02:00
329c8d999a cosemtic adaption of interactive.py
Change-Id: Ie8697e06a2f70e40a8b451cb42e8d0fb12a0b40f
2023-04-21 16:18:10 +02:00
af920aafdf update interactive client
Change-Id: Iacc521807969b23779f1172d09bfc164a0a66a3b
2023-04-21 16:17:31 +02:00
98162a59b9 add frappy-cli
Change-Id: I2622212e178ceede7cc7285564a7a09929fafc5c
2023-04-21 16:17:31 +02:00
64b4cba67c fix error from manual %-format conversion
frappy_psi.ppms (line 263) was not correctly converted probably due
to the fact, that dict access with f-strings gets quite ugly.

As it seems we want to get rid of %-format, use str.format_map here.

Change-Id: Idf5b700554aa7a02a6647dc4672bf4a3856f92a5
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/30933
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>
2023-04-21 16:17:31 +02:00
d99f2dfbc3 Add __format__ to EnumMember
Make the format specifier 'd' able to be used when formatting them
in f-strings as an alternative to an int()-cast.

Change-Id: I4083aa0f4b0d8d10e3e11a29591cfbf5e5aca03d
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/30902
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
Reviewed-by: Alexander Zaft <a.zaft@fz-juelich.de>
2023-04-21 16:17:31 +02:00
e673dfe92b [deb] Release v0.17.10 2023-04-21 16:17:30 +02:00
d18e368ab9 make entangle mapping a dict
Change-Id: I38d863a907469674001f0721140f88c17b53635b
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/30911
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
Reviewed-by: Alexander Zaft <a.zaft@fz-juelich.de>
2023-04-21 16:17:30 +02:00
a334cc4f0a Manually convert most remaining format statements
%d accepts floats and other things, so manual fixes are needed after
conversion.

After flynt -ll 2000 --aggressive, each was manually checked if the
casts with int() are needed.

Two statements are still missing in ls370res

Change-Id: I2651ddbe60695aa19582882a97d0f71bcb05c1ef
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/30901
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Alexander Zaft <a.zaft@fz-juelich.de>
2023-04-21 16:17:30 +02:00
42c5e97122 Rebuild NodeWidget when the description changes
+ add descriptionChanged signal
* track detached of TabWidgetStorage correctly
* build new NodeWidget when the nodes description changes. the old one
  is replaced

Change-Id: I61e60c61b7c2c975819730cb98562657a66f16af
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/30910
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
Reviewed-by: Alexander Zaft <a.zaft@fz-juelich.de>
2023-04-21 16:17:30 +02:00
f069b31d5a [WIP] gui: add specific input widgets
+ add bool, enum and generic input widgets

Change-Id: If6962ed6f2c96a319d4ed8c8ee5fb8c03f6e7f82
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/30723
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Alexander Zaft <a.zaft@fz-juelich.de>
2023-04-21 16:17:30 +02:00
47000f7a9f move unit into display label
TODO: decide if this is preferable to a dedicated label
Change-Id: I707fad5ab85bf2de53f82bc638a9fe20eb26f14e
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/30786
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>
2023-04-21 16:17:30 +02:00
6f95c0d825 Convert formatting automatically to f-strings
Automatically convert formatting with the following call:
flynt -ll 2000 -v frappy*
Result: 303/381 auto-converted.
Failing conversions will be looked at manually in a follow-up commit.

Change-Id: Icd996b27221202faccc15af78e0380cf52ee37f2
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/30900
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Georg Brandl <g.brandl@fz-juelich.de>
Reviewed-by: Alexander Zaft <a.zaft@fz-juelich.de>
2023-04-21 16:17:30 +02:00
a9d479ba0a Change leftover %-logging calls to lazy
Change-Id: I0bee8d02ac364ab93f77919cae78afa386b85c85
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/30899
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Alexander Zaft <a.zaft@fz-juelich.de>
2023-04-21 16:17:30 +02:00
9329d2b0f4 [deb] Release v0.17.9 2023-04-21 16:17:30 +02:00
4f69899fbe interactive client: avoid messing up the input line
- trigger a redraw of the input line when asynchronous log
  messages arrive
+ do not print traceback on 'remote' errors
+ persistent readline history

Change-Id: If85fd064c1c09c44e0cb0ebccbfc1b6411ad5aac
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/30793
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
2023-04-21 16:17:30 +02:00
2dad4b4ee1 [deb] Release v0.17.8 2023-04-21 16:17:30 +02:00
5174321b6a Debian: Fix typo
Change-Id: Iba218600bd60ad663869cee7758d6d4ac1387908
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/30864
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Jens Krueger <jens.krueger@frm2.tum.de>
2023-04-21 16:17:30 +02:00
32edc4c049 [deb] Release v0.17.7 2023-04-21 16:17:30 +02:00
d986134781 Debian: add pyqtgraph dependency
The pyqtgraph version should be >= 0.11.1 (tested on Debian 11), so the
buster installation won't run without manual interaction: Installation
of pyqtgraph via pip

Fixes: #4711

Change-Id: If785da5578ac7f3812d8d49bc36d055cde4a37f9
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/30859
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Jens Krueger <jens.krueger@frm2.tum.de>
2023-04-21 16:17:30 +02:00
bd4330f9fd [deb] Release v0.17.6 2023-04-21 16:17:30 +02:00
f260bd25b1 Fix debian GUI package dependency
The current python packages for Qt5 are called 'python3-pyqt5*'

Change-Id: I36bba2be50eb8769b01eeaf1202b32d3c849fc56
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/30854
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Alexander Zaft <a.zaft@fz-juelich.de>
2023-04-21 16:17:30 +02:00
d0af831b1f improve error messages
- treat validation errors of the result of write_ and read_ messages
  properly
- add info about the called read_* and write_ methods to the error
  message, in case the error is not raised in the outmost method
- as subsequent errors in poll functions are logged only once, log an
  info when a poll function succeeds again
- remove DiscouragedConversion error

Change-Id: Ib66e001cc95de8225751a1464a92594c369ceb3f
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/30788
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
2023-04-21 16:17:30 +02:00
df8bc1c203 cli: add argparse and inlcudes before repl
+ add argparse to cli
+ add option for files that are executed after connection to the modules
  but before repl starts
+ add option to skip interactive mode after executing files

Change-Id: I8f01db84b2c91d4bf1a7b397e8fa1bf0c87ddf0d
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/30823
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Tested-by: Markus Zolliker <markus.zolliker@psi.ch>
Reviewed-by: Alexander Zaft <a.zaft@fz-juelich.de>
2023-04-21 16:17:30 +02:00
c101af99d3 add optional validation to ValueType
* add optional parameter for ValueType: validator
used for checking, if a value meets a criteria (e.g. is dict)
+ InternalParameter, which is not exported and can hold any python value

Change-Id: If39a7a4a8019f2aa1a930e42cbef4fca59163b78
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/30787
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Alexander Zaft <a.zaft@fz-juelich.de>
2023-04-21 16:17:30 +02:00
7baacb0f9e add copyright headers where missing
Change-Id: Ie68ed439d084b4b79d7db81b58360967f5726d7c
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/30768
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Georg Brandl <g.brandl@fz-juelich.de>
Reviewed-by: Alexander Zaft <a.zaft@fz-juelich.de>
2023-04-21 16:17:30 +02:00
155efb06bf gui: logwindow improvements
* make state consistent with menu action
* colorize
* add time to log message

Change-Id: I0c0800ddb1843b826a6adacf4e4b18d52598acf2
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/30759
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
Reviewed-by: Alexander Zaft <a.zaft@fz-juelich.de>
2023-04-21 16:17:30 +02:00
2f730ab444 gui: add console history
+ add HistorySerializer to merge histories in correct order
* move console to own file
* promote msgLineEdit to class based on NICOS-HistoryLineEdit

Change-Id: I853d49a70640f38275c8762ab345003db5ec5592
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/30753
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
Reviewed-by: Alexander Zaft <a.zaft@fz-juelich.de>
2023-04-21 16:17:30 +02:00
41f3a2ecd4 gui: switch group button order, change text
* change order of name and toolbutton
* change text on click

Change-Id: I6efba544d0ba63fd8065483a566643372892cc00
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/30750
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Alexander Zaft <a.zaft@fz-juelich.de>
2023-04-21 16:17:30 +02:00
10d2d16453 gui: full module description only in detailed mode
* take only the first line of the description, this makes the normal
  view more condensed if it is formatted like a git commit message or
  similar

Change-Id: I268dce0aa09d3ad5133815fe33577532bf0a2e96
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/30749
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Alexander Zaft <a.zaft@fz-juelich.de>
2023-04-21 16:17:30 +02:00
087a5ef476 gui: show parameter properties again
* parameter properties shown with own button
* for now as a popup window

Change-Id: If3b51eb66a759c207591f1341126557f2c6e4a3d
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/30748
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Alexander Zaft <a.zaft@fz-juelich.de>
2023-04-21 16:17:30 +02:00
67bbe9d9a5 [deb] Release v0.17.5 2023-04-21 16:17:30 +02:00
0b501ba444 Fix generator
Change-Id: Ic332e9c130b56e446787af87718d4028f2aac714
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/30746
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Alexander Zaft <a.zaft@fz-juelich.de>
2023-04-21 16:17:30 +02:00
981513df10 [deb] Release v0.17.4 2023-04-21 16:17:30 +02:00
95e5102137 Fix entangle integration bugs
found during ccr-box adaption
* missing param description DigitalOutput
* missing Writable baseclass in DigitalOutput
* rework ccr config file

Change-Id: Ie40f875caacd374b02e6f6175b5fb003619c5f4e
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/30743
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Alexander Zaft <a.zaft@fz-juelich.de>
2023-04-21 16:17:30 +02:00
d498fa9230 [deb] Release v0.17.3 2023-04-21 16:17:30 +02:00
eff8ac8c62 [deb] Release v0.17.2 2023-04-21 16:17:30 +02:00
39f0180f40 format unit properly in the case of nested arrays
The unit is shown at the end of nested arrays.

Change-Id: I235bc40e61161e09a7b00cd1e186d8d8c1769d89
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/30734
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
2023-04-21 16:17:30 +02:00
dd6ee398f2 fix issues when closing tabs
- catch error on AsynTcp.shutdown (when already disconnected)
- avoid opening duplicate tabs
- shorten try except in mainWindow._handleTabClose to the minimum
+ fix an issue with frappy.client.CacheItem.formatted

Change-Id: Ice4086373a89a969f02e08ec90a173edbd5b0585
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/30730
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
2023-04-21 16:17:30 +02:00
c3796aa6eb gui: more greeter interactions
+ double click item to open node
+ Enter key while recent nodes are focused opens selected

Change-Id: I397f743faec70e623b5ef9a86d61625f8db7e933
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/30736
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
Reviewed-by: Alexander Zaft <a.zaft@fz-juelich.de>
2023-04-21 16:17:30 +02:00
4cf54dcce8 Add SECoP link to readme
Change-Id: I5990b59e26060f9c9a2457f455537a0af28e28bb
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/30735
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Alexander Zaft <a.zaft@fz-juelich.de>
2023-04-21 16:17:30 +02:00
79277e86f1 fix generalConfig defaults
bin/frappy-server:
- the default for omit_unchanged_within must not be overriden
+ remove no longer used disable_value_range_check

Change-Id: I5c0620e44dc7df3ae2ca48f7ab6371189acae489
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/30727
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
2023-04-21 16:17:30 +02:00
361f9ac4fc remove UNKNOWN, UNSTABLE and DISABLED from Readable.status
- re-add them where needed (epics, entangle ...)

Change-Id: I2b8af9f5f86285f081d5418211f6940e80a1dbd7
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/30718
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>
2023-04-21 16:17:30 +02:00
11a3bed8b8 improve frappy.errors
- include all secop errors from spec
- add doc strings
- make conversion to and from error report nicer
- move all error classes to frappy.errors
- rename errors clashing with built-in errors

Change-Id: I4d882173b020cd4baf862c5891375b691e67e24a
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/30721
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
2023-04-21 16:17:30 +02:00
5db84b3fa1 simplify status type declaration
- StatusType: simpler inheritance (inherit from module instead of Enum)
- StatusType: more robust for standard codes, give names only
- <Module>.Status is automatically extended
- Enum: accept duplicates with same name and value

Change-Id: Iad1dacf14c31fe6f4ae48e7560b29e49838e4f23
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/30716
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>
2023-04-21 16:17:30 +02:00
dccd329435 enhanced parameter range checks
- check for <param>_min and <param>_max
- customized checks with check_<param> method

the inherited customized check functions are all called in
a sequence, with the possibilty to return True to quit earlier,
no need to use super calls here

Change-Id: I903081abbbad2586c1e8237e303abaa3683ac419
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/30632
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
2023-04-21 16:17:30 +02:00
0d265b9752 make return value 'Done' unneccessary
'Done' was introduced in order to suppress unneccessary
duplicate updates. However, since super calls on access methods are
allowed, it is not nice when such a method returns Done, as this
is not automagically replaced by the current parameter value.
As a consequence:

- using Done is discouraged, but not (yet) removed in all code
- the 'omit_unchanged_within' property is moved from Module to an
  internal Parameter property 'update_unchanged'
- its default is moved from a SEC node property to generalConfig
- the 'update_unchanged' parameter property may be set to
  'never' for parameters where duplicate updates make no sense
- this property might be set to 'always', for measurements, where
  even unchanged values taken from HW should be transmitted

Change-Id: I2847c983ca09c2c4098e402edd08d0c96c3913f4
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/30672
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
2023-04-21 16:17:30 +02:00
9cab6670b9 check configured names for spaces
reject equipment_ids and module names which contain spaces

Change-Id: I0ffe8fee13122ddfff6715863d0a82f72b5cb9f6
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/30711
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Alexander Zaft <a.zaft@fz-juelich.de>
2023-04-21 16:16:40 +02:00
8c6f1a21ab gui: terminate connection on tab close
+ add method to explicitly terminate connection on qsecnode
* disconnect explicitly when closing tabs

Change-Id: I00af5fb296e1499b88eafc033f62fdcc1131a145
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/30677
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Alexander Zaft <a.zaft@fz-juelich.de>
2023-04-21 16:16:40 +02:00
a1271b7268 fix README typo
Change-Id: I545835243959c9feb6781f3ed400bff53d0eb428
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/30705
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Alexander Zaft <a.zaft@fz-juelich.de>
2023-04-21 16:16:40 +02:00
9bf187758b central point for status codes
Put status code definitions into frappy.datatypes.StatusType.
frappy.datatypes is anyway imported in servers and clients,
so this is a better place than frappy.modules.

Change-Id: I81dfc8a066f598fbd20854ed1a13b937b7facc8c
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/30703
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
2023-04-21 16:16:40 +02:00
349c510555 gui: support proper formatting of values
- use Datatype.format_value to convert all values
- frappy.client.ProxyClient: use CacheItem instead of 3-tuple
- CacheItem has built in formatting
- adapt gui to use it instead of stopgap

As it is now easy to convert to string including values, it may
be better to move the unit in the modulewidget into the value field.
This would simplyfy the code.

Change-Id: I5c06da4a24706fcbc83ebcbf8c0ea6a8eb6d7890
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/30680
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
2023-04-21 16:16:40 +02:00
31b1a916f5 fix importing AsynCon without serial
importing HasIO without serial being installed fails, as the import
guard sets Serial to None, which is then called for AsyncSerials
SETTINGS attribute

Change-Id: I94a9eb5c2ff8de1a1b31f31700358d5d2226eadd
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/30700
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Alexander Zaft <a.zaft@fz-juelich.de>
2023-04-21 16:16:40 +02:00
9755040ac2 demo: fixup Switch class
Target and value are not emitting an update event upon changing target.

Change-Id: Ic01d17fd8529dc0b0a720fbc79d98c7a61fc572b
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/30645
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
Reviewed-by: Georg Brandl <g.brandl@fz-juelich.de>
2023-04-21 16:16:40 +02:00
c0704b3d4f split BadValue into WrongType and RangeError
in order to match SECoP specification

fixes #4668

Change-Id: Ica73a8171536ccc324cf8db915347a6263c2d736
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/30625
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>
2023-04-21 16:16:40 +02:00
58ff438f46 handle connection close more gracefully
Change-Id: Iea12e95e88fa3638f91edc2e99abb5ece3f75b4d
fixes: #4705
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/30685
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>
2023-04-21 16:15:20 +02:00
98ab5a5785 gui: Logo in greeter
+ add SECoP logo resources (3 dark text, 1 light text)
+ Add logo to greeter
TODO: when there is a frappy-logo, replace this

Change-Id: I37eb8946d291b2f5dde0b5000251a4ac363fb01f
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/30596
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Georg Brandl <g.brandl@fz-juelich.de>
Reviewed-by: Alexander Zaft <a.zaft@fz-juelich.de>
2023-04-21 16:15:20 +02:00
58f7ef6de4 gui: make module details button checkable
Change-Id: I22e5591e87e84a86ecbaf47d86d5885cf0a569be
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/30676
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
Reviewed-by: Alexander Zaft <a.zaft@fz-juelich.de>
2023-04-21 16:15:20 +02:00
431a1c20ab isort: add firstparty
Change-Id: I38d2ee3bf617accc97069167c408b1cd1b9ef6b1
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/30675
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Georg Brandl <g.brandl@fz-juelich.de>
2023-04-21 16:15:20 +02:00
2e698af78c Jenkinsfile: isort does not fail the build
Change-Id: Ide31346f35ecef3030f306698cfe4586010df020
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/30674
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Georg Brandl <g.brandl@fz-juelich.de>
2023-04-21 16:15:20 +02:00
5c2b4c6491 Fix doubled module info
Change-Id: Ic406471a28f0c891323b990760ffd721ed419e12
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/30667
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Alexander Zaft <a.zaft@fz-juelich.de>
2023-04-21 16:15:20 +02:00
80b1932f39 treat returning None from write_<param> properly
as intended originally, returning None should be the same
as returning the new value from the argument

Change-Id: I5596d2b6f6f525efd4dc410bbe22dad6f1fb2017
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/30659
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>
2023-04-21 16:15:20 +02:00
644bfa85f7 Short background Color animation on scroll
When the tree selection changes, trigger a short animation of the
background color of the name-label of the selected module/parameter

Change-Id: Ia56619a7e73458a5ac63ef821b5ac7ab5f7451df
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/30651
Reviewed-by: Georg Brandl <g.brandl@fz-juelich.de>
Reviewed-by: Alexander Zaft <a.zaft@fz-juelich.de>
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
2023-04-21 16:15:20 +02:00
bd06abc060 gui: always show scrollbar in nodewidget
To avoid content jumping around when expanding.

Change-Id: I81a80f2ed11057bab2d4f0c1a21f9f29c4053c36
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/30648
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>
Reviewed-by: Georg Brandl <g.brandl@fz-juelich.de>
2023-04-21 16:15:20 +02:00
efad0e2079 gui: cleanup code in modulewidget
- line length
- spacing style
- copyright header

Change-Id: Ib402baf271868e82010e9acdfad4087164d91714
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/30647
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>
Reviewed-by: Georg Brandl <g.brandl@fz-juelich.de>
2023-04-21 16:15:20 +02:00
c3bfc0a930 gui: better display of protocol version
Change-Id: I987aa7bf4b0cb1395425590655739fd4b28bd282
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/30646
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Alexander Zaft <a.zaft@fz-juelich.de>
2023-04-21 16:15:20 +02:00
2eef2f1d7f Remove Modulectrl Widget
Change-Id: I172227f01c8de494477050414e8cdd02bebafdbc
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/30639
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Georg Brandl <g.brandl@fz-juelich.de>
Reviewed-by: Alexander Zaft <a.zaft@fz-juelich.de>
2023-04-21 16:15:20 +02:00
d8621bdc2c Scroll Module area instead of replacing widgets
Change-Id: Id06eaabee294e032a71e7b28255a9b818763737e
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/30638
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Georg Brandl <g.brandl@fz-juelich.de>
Reviewed-by: Alexander Zaft <a.zaft@fz-juelich.de>
2023-04-21 16:15:20 +02:00
23c9aa6038 Extend Node and Module widgets
- put nodeinfo elements into ui file
- add per-module detailed view
- add first version of grouping (cleanup in a follow-up commit)

Change-Id: If35bc4a8f4ed0a313e97f88797e70186d7c0d9bc
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/30631
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Alexander Zaft <a.zaft@fz-juelich.de>
2023-04-21 16:15:20 +02:00
0464d60f68 Merge resource files
- move config editor files into resources
- merge both qrc files into one

Change-Id: I3089b0e1c7784683964d3e0b6e87c8a5cbd62ad2
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/30593
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Georg Brandl <g.brandl@fz-juelich.de>
Reviewed-by: Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
Reviewed-by: Alexander Zaft <a.zaft@fz-juelich.de>
2023-04-21 16:15:20 +02:00
d4b80eabd6 [Needs Feedback] Add PyQt6. Remove PyQt4
- fully qualify enum values for Qt6
- add resource file per Qt version
- TabWidget: use QPointF instead of QPoint for constructing mouse event

Change-Id: I07da61c36c4228a60f6b5b9dacbead27c0a2409d
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/30585
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
Reviewed-by: Alexander Zaft <a.zaft@fz-juelich.de>
2023-04-21 16:15:20 +02:00
952cbe76a5 refresh logging when reconnected while watching
when a connection is reconnected, send 'logging' has to
be resent when watching

Change-Id: I13bb0075811151d93bd20f390b4c0745b9ad5418
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/30604
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
2023-04-21 16:15:20 +02:00
746df2eb94 improve online help of frappy-cli
- help text shown exactly once (even with no or more arguments)
- automatically generated client object names
+ stay in interactive mode even when not all clients succeded

Change-Id: Iefcac66df92f47363e43bc9b97bb2082f153e5df
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/30583
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>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
2023-04-21 16:15:20 +02:00
494960a2ac Always make a greeter tab
Change-Id: If6a8cf55d29508645e4a731277ad341de23ca37d
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/30587
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
Reviewed-by: Alexander Zaft <a.zaft@fz-juelich.de>
2023-04-21 16:15:20 +02:00
0382313fc7 gui: better label for param set button
Change-Id: Ied1eb2851b755fa63289dcc19ba8b2aff803f13e
2023-04-21 16:15:20 +02:00
66faf89d7f gui: remove unused ui file
Change-Id: I2b73cb7e65d12f5b053274e50d2fcf5f0b01c6d0
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/30581
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>
2023-04-21 16:15:20 +02:00
79da402ceb AsynConn.uri: better handling for missing scheme
- check roughly for hostname being a valid address
- allow missing 'tcp' scheme even with missing port number

Change-Id: Ia3ce4cb7b8d2a4b339421eafe21f06fba6d938e6
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/30582
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
2023-04-21 16:15:20 +02:00
b0b85a2711 Make input field more distinct
* different color for value fields
* placeholder text in input fields

Change-Id: Ibb94eb09ab7bfd7c2807f27226b7e2825ac3ea5d
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/30579
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Georg Brandl <g.brandl@fz-juelich.de>
Reviewed-by: Alexander Zaft <a.zaft@fz-juelich.de>
2023-04-21 16:15:20 +02:00
5fb1e649ab Driver for ThermoFisher A 10
Change-Id: Ic19ae444c3b4242f3bb1fe83852d4521326d0b9d
2023-04-13 15:14:44 +02:00
7a3cfe9836 fix %g in the write_target
Change-Id: I3c15ed44848c792320dd146d17d4bb52f30f49f0
2023-03-08 14:57:22 +01:00
e12f2bacb1 fix read_setpoint
Change-Id: I8c6449979aca294af13c350b7632190121bc4230
2023-03-08 14:52:16 +01:00
d6c641e67b fix set/get_par
Change-Id: I9790a667bc2e89baf2be9e35edb7473cdfc5dd31
2023-03-08 14:49:56 +01:00
ec15a35977 lakeshore ramp
Change-Id: I323c2b88e554bc2dc9c3f1af2b23f99aa082489f
2023-03-08 14:37:56 +01:00
05415e79b1 qnw: WARN status when control is off
Change-Id: Ic91607658b4c0f3a622ad7307aa55d927c82914b
2023-03-06 14:05:01 +01:00
908f2d4c23 fix minor issues in ppmssim and k2601b
merging changes in gitpsi and gitmlz branch

Change-Id: I4996da637ad62c5bd76596ea95aee1decaaccaf2
2023-03-06 08:24:16 +01:00
cdd6d7f1a2 improved features
- remove legacy feature proposals
- add Feature.featureName (in case not matching ptyhon class name)

Change-Id: I7a09fc5e24067b2fde5d2c04523bc5d2172e714b
2023-03-06 08:24:16 +01:00
8831e7142f Remove unneeded constants
Change-Id: I1bfb213bb6c58af311d4f1af7af18abbdb82c269
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/30575
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Alexander Zaft <a.zaft@fz-juelich.de>
2023-03-06 08:24:16 +01:00
e5c99c36e7 Fix Node adding logic
* Unify _addNode calls
* introduce addNode for error catching
* set parent for Nodewidget only after successful creation (otherwise
  this results in Bug #4694

Change-Id: I82a5d867b45e766cd7dd33e7144e57a9f66a73b3
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/30572
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Georg Brandl <g.brandl@fz-juelich.de>
Reviewed-by: Alexander Zaft <a.zaft@fz-juelich.de>
2023-03-06 08:24:16 +01:00
bb0619f212 gui: isort
Change-Id: Ic1c1d54a9577c1774024aefd5464b6e4b8e1fe07
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/30570
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Alexander Zaft <a.zaft@fz-juelich.de>
Reviewed-by: Georg Brandl <g.brandl@fz-juelich.de>
2023-03-06 08:24:15 +01:00
0e5f62d849 gui: move QSECNode to separate module
Change-Id: Iaba98c6c74adec7c3de952cc703430c5756dfbe4
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/30569
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Georg Brandl <g.brandl@fz-juelich.de>
2023-03-06 08:24:15 +01:00
3aaa12eee1 gui: add about dialog, remove "about Qt" menu entry
Change-Id: I483cc8cbe67cff6d0e14ff9ce198cc50bebb7cf8
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/30567
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Georg Brandl <g.brandl@fz-juelich.de>
2023-03-06 08:24:15 +01:00
dbf4ad373e readme: fix make call
Change-Id: I58f31d53a2a4c67680c848f9e81068daabfa6cd4
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/30568
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Georg Brandl <g.brandl@fz-juelich.de>
2023-03-06 08:24:15 +01:00
595c32783c Add reconnect Action
* add reconnect method to QSECNode
* Rename File Menu to Node
* Add Button to 'Node' Menu

Fixes: #4687
Change-Id: I04ee55ddbc13253255f0099a9f4d2f8d4d0da77c
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/30566
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Georg Brandl <g.brandl@fz-juelich.de>
Reviewed-by: Alexander Zaft <a.zaft@fz-juelich.de>
2023-03-06 08:24:15 +01:00
b48b965070 make doc should not fail when version is not available
Fixes: #4665
Change-Id: Ief312e86b64ec1290d3410e1374ad1a71b5a3e4e
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/30536
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Georg Brandl <g.brandl@fz-juelich.de>
2023-03-06 08:24:15 +01:00
2cf073e7d8 installer: add config for frappy gui exe
Change-Id: I1abc325f685f8e95fac281b419be3defcc1e4485
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/30508
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Georg Brandl <g.brandl@fz-juelich.de>
2023-03-06 08:24:15 +01:00
ef1664e1a2 cfg editor: fixes
Change-Id: I1ea9f96f6c39c25ff8d4c51163aabccc5698dace
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/30507
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Georg Brandl <g.brandl@fz-juelich.de>
2023-03-06 08:24:15 +01:00
05cf4a791a demo lakeshore with simulation
- example for lakeshore tutorial
- lakeshore simulator (merge with frappy_psils370sim)
- rename stringio-server to sim-server

Change-Id: I33a9c75ea268349573f8a8387910921e19f242eb
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/30516
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
2023-03-06 08:24:15 +01:00
c8f30582a5 default settings on the IO class
allow to define default settings on the IO class:
- a default 'port' may be given for tcp
- defaults like 'baudrate' or 'parity' might be given
  for serial connections

this avoids explicit settings in the config file in case
the settings can not be changed or have a typical value
other than the defaults in serial.Serial

Change-Id: I990f47d63e785f8cc48c4af197944a8eebe91fb4
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/30555
Reviewed-by: Georg Brandl <g.brandl@fz-juelich.de>
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
2023-03-06 08:24:15 +01:00
0acb80380a fix ppms with proxy
- proxy: remote parameter status should overddide datatype from
  ProxyModule.status
- ppms: create PpmsDrivable instead of importing from
  (PpmsBase, Drivable). Order matters for status parameter!
- update cfg files

Change-Id: If8fc263cffb903d8b3c1a93a089dcac3597d13a0
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/30512
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
2023-03-06 08:24:15 +01:00
b5205d7c7a better guess for cfg file location
check for existence of repodir+'/cfg' instead of repodir+'/.git'

fixes #4692

Change-Id: Ida841cd31eba851305bcfc0d96019710340c3140
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/30510
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
2023-03-06 08:24:15 +01:00
819026659a Fix forge link in README
Change-Id: Ie428f1814edc2685d03590da4e0635d0b100bbdf
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/30538
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Alexander Zaft <a.zaft@fz-juelich.de>
2023-03-06 08:24:15 +01:00
09f6149822 version.py: sync with other projects
Change-Id: I0e8b1502a9d1e8efa3ee1e0798ec5a85bf9d0f35
2023-03-06 08:24:15 +01:00
0ce5a495f0 do not reuse address on Windows
socketserver.ThreadingTCPServer.allow_reuse_address must be False on
Windows systems, else several servers might be started on the same
port, and unspecified behaviour will happen

Fixes: #4695
Change-Id: Ic9f193e23854f9cd3413ab6e664ca8029a7c9c76
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/30523
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>
2023-03-06 08:24:15 +01:00
a66f2ac21b Remove auto-connect to 10767 on startup
* not necessary with greeter
* Port is default in greeter text field

Change-Id: If89cb72ec6af20fd4d83488af85b942c5f54b105
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/30531
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Alexander Zaft <a.zaft@fz-juelich.de>
2023-03-06 08:24:15 +01:00
244d84aa56 Add recent SECNodes to Drop down menu
Fixes: #4686

Change-Id: I864e8dd407b23c8af0f0633d3be477e222df6c40
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/30525
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Alexander Zaft <a.zaft@fz-juelich.de>
2023-03-06 08:24:15 +01:00
f1ea85bfa7 Add greeter tab to UI
* adds a tab on startup that shows last connected secnodes
* last nodes saved with qsettings

Change-Id: I2b663a408dc46bd0a0135e723b55d5ef3661bec8
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/30524
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Alexander Zaft <a.zaft@fz-juelich.de>
2023-03-06 08:24:15 +01:00
4c577cf83d allow super calls on read_/write_ methods
instead of wrapping the access methods on the class directly,
create a wrapper class with the wrapped methods.

Change-Id: I93f3985bd06d6956b42a6690c087fb125e460ef9
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/30448
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
Reviewed-by: Alexander Zaft <a.zaft@fz-juelich.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
2023-03-06 08:24:15 +01:00
ca4297e3a6 add pyqtgraph to gui dependencies
Change-Id: Ic75d395e683c4c4e92162a287ef9beeb1c68df53
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/30509
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
Reviewed-by: Georg Brandl <g.brandl@fz-juelich.de>
2023-03-06 08:24:15 +01:00
31706093fb cfg path quick fix for hands-on session
Change-Id: If79f76ed17dce6a5587deaae2eae4eda77546306
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/30503
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
2023-03-06 08:24:15 +01:00
f4fac1f3dd Fix Simulation and Proxy
Combination of changes 30188 and 30194 leads to simulation and proxy
still reading 'default' instead of 'value'

Change-Id: I176a42f534a4eb04916b57bb3b54e880a2531ed3
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/30502
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Alexander Zaft <a.zaft@fz-juelich.de>
2023-03-06 08:24:15 +01:00
c9500bcd96 [deb] Release v0.17.1 2023-03-06 08:24:15 +01:00
65c5277a79 gui: make plot windows children of the node, so they close automatically
Change-Id: I025bff02bc566be8bbaa8d90bf0035d1e2bf2a69
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/30494
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Georg Brandl <g.brandl@fz-juelich.de>
2023-03-06 08:24:15 +01:00
e0132ed201 gui: clear tree selection by clicking into empty space
Change-Id: Ib065feeffa8636ee0b3160d7612f069057ef6b0e
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/30492
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Alexander Zaft <a.zaft@fz-juelich.de>
Reviewed-by: Georg Brandl <g.brandl@fz-juelich.de>
2023-03-06 08:24:15 +01:00
e0090a5918 gui: as a stopgap measure, apply %g format to floats
Needs to be properly fixed by sharing the code to format
parameter values everywhere.

Change-Id: I766e4d2cb644153f99f9f40ce0414aa314b47307
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/30491
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Georg Brandl <g.brandl@fz-juelich.de>
2023-03-06 08:24:15 +01:00
ee9f8536b2 gui console: better formatting of input/output
Change-Id: I2ffb3712bb4ef5dcdfbcae869e4971bdc7a116ad
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/30490
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Georg Brandl <g.brandl@fz-juelich.de>
2023-03-06 08:24:15 +01:00
a1141c87c9 fix links in doc/introduction
Change-Id: I9f613c77835472c79be2850265126686cb57e8b9
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/30493
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
2023-03-06 08:24:15 +01:00
b75d89ad16 gui: make spacing more consistent
Change-Id: I5bb44c440b33cb0b5de0f5e2457c9226afe74a50
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/30489
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Alexander Zaft <a.zaft@fz-juelich.de>
Reviewed-by: Georg Brandl <g.brandl@fz-juelich.de>
2023-03-06 08:24:15 +01:00
af32fd308c cfg: repair demo cfg after conversion
Change-Id: I00b4e92d8e10842e9b2ae1ae904402473e77107f
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/30488
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
Reviewed-by: Georg Brandl <g.brandl@fz-juelich.de>
2023-03-06 08:24:15 +01:00
355eaeb864 config: demo config fixes
Change-Id: I380e101065179bf0d784bacfe6b79731b40af4c5
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/30487
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
Reviewed-by: Georg Brandl <g.brandl@fz-juelich.de>
2023-03-06 08:24:15 +01:00
a2458005f1 gitignore: ignore demo PID file
Change-Id: Iab6bd32c51772f5bcf256802c9bb7082be5e7873
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/30486
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
Reviewed-by: Georg Brandl <g.brandl@fz-juelich.de>
2023-03-06 08:24:15 +01:00
1004512caf [deb] Release v0.17.0 2023-03-06 08:24:15 +01:00
9f54d89efa Rework GUI.
Work-in-progress state of the new gui.

Change-Id: Ib5e9ad2178b372fbd2914077096a9c73f025ecb7
2023-03-06 08:24:15 +01:00
72f9f242d7 doc: fix trailing comma in authors
Change-Id: Ie611a3258a5b1acbdcbdfa55aad5198c4613f647
2023-03-06 08:24:15 +01:00
4857ebf043 ci: remove duplicate variable
Change-Id: I839ac4d9eda36082ba24c7c06df234a78798be2c
2023-03-06 08:24:15 +01:00
25ff6018c4 [deb] Release v0.16.1 2023-03-06 08:24:15 +01:00
b3fbf3b107 [deb] Release v0.16.4 2023-03-06 08:24:15 +01:00
8651a5fb83 [deb] Release v0.16.3 2023-03-06 08:24:15 +01:00
231f5942f8 [deb] Release v0.16.2 2023-03-06 08:24:15 +01:00
1d893974cb gui: move icon resources for the cfg editor to its subdirectory
Change-Id: Iebda39c73d9886701a58fe58b91ef54b70a4fd73
2023-03-06 08:24:15 +01:00
6b32c4bb81 [deb] Release v0.16.1 2023-03-06 08:24:15 +01:00
9b801e9487 add frappy-cli to package
Change-Id: I8697f05517935470a7c867ebddd63f5d28be21c3
2023-03-06 08:24:15 +01:00
23b1f59320 [deb] Release v0.16.0 2023-03-06 08:24:15 +01:00
3a2a90e712 README: add link to doc on forge.frm2.tum.de
Change-Id: I4a183202590a7e73ee1a905538c35af175f6c82d
2023-03-06 08:24:15 +01:00
ad7ff7f7f3 do proper value import on the client side
json values were not converted properly, resulting in integers
instead of enums at the client side

+ add log.debug in rx thread

Change-Id: Ifc3c3b28540eb6a516d77387a3f83711f34b7480
2023-03-06 08:24:15 +01:00
8799710b38 Convert example configs to python
Fixes: #4627

Change-Id: I1049af9fa2f3f9ec06e55811dc9ecfa5f359c805
2023-03-06 08:24:15 +01:00
e1d5170a90 T controller tutorial and improve documentation
add tutorial for Berlin hands-on workshop

+ improve the documentation (hints for structure welcome)
+ remove 'optional' parameter property
  (is not yet used - should not appear in doc)
+ added test property in frappy_demo.cryo alters Parameter class
  ('test' property appears in Parameter doc)

Change-Id: I3ea08f955a92f72451fd23a5ff00d1185c7fb00e
2023-03-06 08:24:15 +01:00
a39db9a35d Change Readme title
Change-Id: Id8b07e76cb5748aa595fb484d78ffcc43f9cdddf
2023-03-06 08:24:15 +01:00
5c874483a9 Add initial README
Change-Id: I314b6bf527ba6bb7011804a3768e7785ed4046e3
2023-03-06 08:24:15 +01:00
65480cd773 HasStates: fix status code inheritance
use mro for status code inheritance
- as a consequence, the decorator class 'StatusCode' is now a
  decorator function 'status_code'. snake case is anyway more
  common for decorators.
- adapt tests
+ fix an error message

Change-Id: Ib409b963c51e0fe807397ff1d73d77d0147b8580
2023-03-06 08:24:15 +01:00
7d59345f43 HasControlledBy and HasOutputModule mixins
for supporting coupled modules (output - input case)

Change-Id: I58488faa0d52b3b984a3dc70ff44ee4a9a218d7a
2023-03-06 08:24:15 +01:00
bc553f889c Implement ramping qnw
Change-Id: I9d7fab73194a0a8be3a230cc7ca99066d2553fce
2023-03-02 17:01:43 +01:00
cae225df41 create qnw driver
Change-Id: I7b466caf91b7a2a177fa94ac84cdfc315475f959
2023-03-01 17:49:15 +01:00
3a52f9d31c fixes in lakeshore alarm
Change-Id: Ibbcbe94d9a64d1c074025183e1310506a2cb00f4
2023-03-01 17:49:15 +01:00
4ebb15ca6c add lakeshore demo for hands-on workshop
- a simple LakeShore model 336 driver

a tutorial follows

Change-Id: I291a615efa5bd58a0dd908949210086d2f82c2ca
2023-02-20 12:52:05 +01:00
240c4f027b interactive client: improve watch function
- watch is now a command, not a module method
- finish watching with ctrl-C
- watching an io module logs communication
- add bin/frappy-cli to start interactive client
+ remove sorted function from StructOf.format_value

Change-Id: I7dd707473e4534f2d39c5d6afc533c2d872380f8
2023-02-20 12:51:55 +01:00
f7e24f13bb raise ProtcolError when specifier is missing
- fixed this for 'read', 'change' and 'do' message
+ fix an error in frappy.client.SecopClient closing the connection
  when the identifier is None

fixes: #4672

Change-Id: Iaba0f9ed86b6eb6ef7588403ba640ded552dded6
2023-02-20 12:51:22 +01:00
c3d8068e02 adapt tutorial to new config file format
Change-Id: Iafd8a2dbed8cd1283ce97b6559138c7bc02714c7
2023-02-20 12:51:11 +01:00
084e890645 do not throw ZeroDivisonError when pollinterval is 0
fast_interval might be 0, indicating to poll as fast as possible
- this should not throw a zero division error

Change-Id: I26e18f5a656c943b906c6ffff65361e1fcf16d50
2023-02-20 12:50:38 +01:00
edd3437682 improve persistent parameters
A value given in config overrides values read from the persistent data file.
To let the loaded parameter have precedence, configure a default only.
The write_<param> method of a persistent parameter is now always called
on startup.

- add tests for persistent behaviour
+ simplify Modules.writeInitParams: remove started_callback argument

Change-Id: I08b49de52e9d9a2ed0918018eb2fe538141a4f5e
2023-02-20 12:50:19 +01:00
c2728c8340 Add .desktop file
Fixes: #4632
Change-Id: Ie0f07049462ed6664cca4062bced09c15bdd5a56
2023-02-20 12:50:08 +01:00
725820fafd fix copy method of Attached
a copy of 'Attached' must also copy the basecls

Change-Id: Ia80cc458b241cb1f224f4c24f0241ed1d4ec1060
2023-02-20 12:49:53 +01:00
9f653b4e6f update interactive client
Change-Id: Iacc521807969b23779f1172d09bfc164a0a66a3b
2023-02-02 17:35:39 +01:00
ae7d3514a1 update interactive frappy client
Change-Id: Icf0ce4c7e2f99ac65e2b47e8fa1efb497322922a
2023-02-02 17:33:53 +01:00
6b9d4a8140 .
Change-Id: I45df6909814299a26eefdb25c63b827741872d82
2023-02-02 17:31:24 +01:00
af295b12b0 fixed write_alarm
Change-Id: I1c155a63c7340aa1292bc1e530cedfcc72d5ffed
2023-02-02 17:25:34 +01:00
69dd011260 Added alarms
Change-Id: Idd06278e44e01522ddf904b56a452ce8c704b5a6
2023-02-02 17:22:08 +01:00
d62076128a add comma between command and arguments
Change-Id: Ibc3a9072140842d521ebb4840eecb180e69b134e
2023-01-31 09:42:45 +01:00
38ae301cda add frappy-cli
Change-Id: I2622212e178ceede7cc7285564a7a09929fafc5c
2023-01-30 17:16:08 +01:00
2ee9ea65da fix SETP?
Change-Id: I337adece10204ca67dcf720e4de36085b80601ba
2023-01-30 17:13:52 +01:00
0dfaa79f77 fix set_par/get_par calls
Change-Id: Iff6b244ca381849333646853b4c0bb8a347bbb3b
2023-01-30 17:09:46 +01:00
866fe73f2e fix CDISP
Change-Id: I09010a1997799bed04f25a7a3eb0157f7af5ad8b
2023-01-30 17:04:28 +01:00
f5c96214b0 Merge branch 'develop' of gitlab.psi.ch:samenv/frappy into develop 2023-01-30 16:57:29 +01:00
4d28abe141 fix issue with old reading
- old reading shoould be ignored
- fix channels for CTI7
2023-01-30 16:56:31 +01:00
c75d7f17f7 The PID parameters were added.
Change-Id: I67a7db66ca13b60d35cb4041bbd35c6c4729416c
2023-01-30 16:52:06 +01:00
a384664639 first version of lakeshore 340 driver
Change-Id: Ie9a68be61e142802b2e71420b87623b7a4b4f645
2023-01-24 18:13:34 +01:00
ee708f7b02 fix typo in frappy_psi.mixins
Change-Id: Ifccb6278bbfc221882067a2ae8c72d0163e5f351
2023-01-24 18:12:25 +01:00
edc942cb24 introduce frappy_psi.mixins
containing for now: HasControlledBy, HasOutputModule

Change-Id: I27e3a81d6e985c74440eb9ea6d224272bac4fb7d
2023-01-24 15:33:27 +01:00
dcd79506a9 Merge "improve parameter initialisation" 2023-01-23 08:25:36 +01:00
f4e974f46c improve parameter initialisation
- make 'value' a Parameter property instead of an attribute
- use 'value' instead of 'default' property for setting
  the initial value in the config file
- removal of initwrite parameter property

this change is the basis of a better implementation
for change 30041 (PersistentParam property 'override_cfg')

Change-Id: I2b82bdd54c2dacb87dcd2b3472004d2f0a730cf0
2023-01-20 16:55:06 +01:00
d889401697 Revert limit change in demo
After 29724, the change in frappy_demo/modules.py from 30183 which was
made to run 'make demo' without errors can be reverted.

Change-Id: I00a6f512304a3159c10e44aef670ac0edd4703d7
2023-01-19 15:39:33 +01:00
85295a7d72 Merge "client: detect original frappy error class" 2023-01-19 12:29:37 +01:00
82957c287d Merge "rework datatypes (setter should not check limits)" 2023-01-19 08:28:30 +01:00
05593d80f6 Bring demo up to date
* Add python config for test and demo server
* Remove old configs
* Fix issue with slow start of test server

Change-Id: If0e576f4e4dda8b03489fdbb79b209dfcdca29ff
2023-01-18 16:24:30 +01:00
7a870aa56c rework datatypes (setter should not check limits)
- use Datatype.validate for converting and checking limits
  (used also in properties)
- Datatype.__call__ converts and validates, but without checking
  limits (used in setter)
- Datatype.validate may be used to add missing optional struct elements
  from previous value (used in Dispatcher._setParameterValue)
- remove problematic range check
+ use shorter formula for converting float to int in ScaledInteger
  (leftover from python2 compatibility)
+ improve error messages (strip very long repr(value))

Change-Id: Ib85736fe558ec3370ebce4e1c43f957e3bb0497c
2023-01-16 10:18:15 +01:00
09d48ea913 Merge "GUI: add logging infra, switch to argparse" 2023-01-12 13:12:09 +01:00
8850edbc2d Merge "Change config to Python" 2023-01-12 13:10:37 +01:00
59cc981566 client: detect original frappy error class
The text part of the error report contains the original error
class - convert it to the frappy error class, if possible.
This makes the error messages on the client nicer, as the
error class would be duplicated in the error message on the
client side.

Change-Id: If2e0c3eb15ac2dd1b80a851ff42e4076557a325d
2022-12-22 13:44:08 +01:00
3cc9a75174 improve He level tutorial
values return from read_* methods should be converted
from string to float on float parameters

+ fix some wording

Change-Id: Ic80010c6fbe3eef23483ff69c8a43e25afb8bb6a
2022-12-22 13:39:47 +01:00
52b77ba9e6 Change config to Python
- Change Configuration format to be python-based.
- move config logic to frappy/config.py
- Add first py-config: cryo_cfg.py
- Adapt test to new expected config format

Change-Id: Iaec484e0e1e21ebbb1e5c74b53be6231329ddf71
2022-12-20 09:48:14 +01:00
db3b190c26 Improve jenkinsfile
Change-Id: I68efdd1a20135a0374fb9692e369a315824786ea
2022-12-19 14:34:48 +01:00
c522c41654 GUI: add logging infra, switch to argparse
First part for #4662

Change-Id: I75877337e8ea35d4c4555471ee4518c942dac88a
2022-12-08 16:38:00 +01:00
929e41ffff Merge "Fix identification response" 2022-12-06 18:00:10 +01:00
8d99a8c536 Fix identification response
- fix header, but accept both responses
- warn when counterpart uses old behaviour

Fixes: #4659
Change-Id: Ib8869755898bf20edcbc7ae93157c943f816ebc1
2022-12-06 14:43:45 +01:00
a14c282993 redesign of the state machine
With the current implementation, we run into a deadlock with the lock
from the state machine interfering with the accessLock on the module.
We can not wait for the state machine to finish while having the
accessLock locked by write_target. As a consequence, when restarting
the state machine we should not wait, but remember the state function
to call and postpone the restart after the cleanup has finished.
For this, we want to know the status before calling the state function.

- create HasState mixin, using doPoll for driving the machine
- StatusCode decorator for assigning a status to a state function
- remove the state machines 'threaded' option
- 'Retry' is now a unique value instead of a class. The retry period
  is determined by the (fast) poll interval.
- return 'Finish' instead of None for finishing the machine. returning
  None for state function is now an error, as this might happen
  easily inadvertently.

Change-Id: Icb31367442f10e98be69af3e05a84f12ce5cc966
2022-12-06 10:18:50 +01:00
d09634a55d Fix error Message for too large arrays
Change-Id: I69b6789ef9f463565918a395120b8f5ad3494b20
2022-11-28 09:28:37 +01:00
b17030afa2 Merge "interactive client: fix detection of overriding modules" 2022-11-10 17:01:34 +01:00
37c9efb27b interactive client: fix detection of overriding modules
+ add docstring to PrettyFloat

Change-Id: Idc92e169e94d0c2bd3f9b8958870393295c87b18
2022-11-10 15:58:28 +01:00
4167ce7b00 fix sorce package name
Change-Id: I91cd5d5e6d2da00eedc3e2ff0ee2a1d3e9ed4b04
2022-11-10 15:00:27 +01:00
2b7b2267d2 [deb] Release v0.15.0 2022-11-10 14:46:02 +01:00
8071c21819 Merge "Fix typo in .description" 2022-11-10 14:43:32 +01:00
e16ef3ae87 Merge "rename debian files" 2022-11-10 14:42:54 +01:00
51147d8e09 Fix doc warnings/errors
Change-Id: Idd6feeb66d58bc562853d3a82831645ef2d5ccf6
2022-11-10 11:12:32 +02:00
6909eb8541 rename debian files
Change-Id: Ib990ecb8ef5ad856eb32110e5448064acf9a5a12
2022-11-10 09:46:46 +01:00
5d6b208671 CI build: upgrade base image
- use python 3.9 (3.9 bullseyse)
- add latex-fonts-extra to fix missing tgtermes.sty
- in bullseye python3-pytango has been renamend to python3-tango
 (https://packages.debian.org/stable/python/python3-tango)

Change-Id: I06c8d8e432644f657057d14bbe754f28e2c10dd4
2022-11-10 09:08:21 +01:00
b3eebb6c6a rename debian package files
Change-Id: I376082a68ceacd6fda984dc8dc1d53fa4afbbfef
2022-11-09 16:31:40 +01:00
7f166a5b8c Rename from secop to frappy
debian/ is still missing, will follow in next commit.

Fixes: #4626

Change-Id: Ia87c28c1c75b8402eedbfca47f888585a7881f44
2022-11-09 16:29:29 +01:00
c1eb764b09 fixed pylint warnings
Change-Id: Ibb3da77e9a53b7293a280659defc029416e30e3b
2022-11-09 17:19:17 +02:00
a9d798fabc Fix typo in .description
Change-Id: I84def1c25279a2492d39931590f32a3b6fea8856
2022-11-08 17:44:58 +01:00
a928c95efd Merge "Add requirements-gui.txt and add PyQT5" 2022-11-03 15:50:19 +01:00
7df4584150 Merge "Remove iohandler left-overs from docs" 2022-11-03 15:00:54 +01:00
b7cebe2cd8 Add requirements-gui.txt and add PyQT5
Change-Id: I8e2cdcc2d0353f4384ba8ea026e02af65064ece9
2022-11-03 14:41:25 +01:00
b8b2dafaf8 Makefile: fix release target
Change-Id: I1cc94a91cbb03c046092a814163664a507c88bdc
2022-11-03 13:56:24 +01:00
f66411dded Remove iohandler left-overs from docs
The iohandler module has been remove in 28526

Change-Id: I24c86f88c8a37f85018cfcdec48279a1438da408
2022-11-03 13:52:27 +01:00
ce4bbec766 [deb] Release v0.14.3 2022-11-03 13:51:52 +01:00
355810a887 MLZ/entangle: fix AnalogOutput.read_status()
Change-Id: I584cd8f559b6c57f3c73105b28bc533526f6f492
2022-11-03 12:59:58 +01:00
aa98604f88 Upgrade for ci
fixes outdated options and macros in Jenkinsfile
fixes outdated options in pylintrc

Change-Id: Ib064cc8b4235536c21288733676438297e15736d
2022-11-03 12:27:37 +01:00
b0051ca3f0 secop_mlz/amagnet: formatting fixup
Change-Id: I05ec4568c87d58f32cf48479ac653075e2211ed3
2022-10-28 16:20:29 +02:00
92edbb27ea change repo to secop/frappy
Change-Id: I515c0d958c87d555156861db83a7b22c60046ead
2022-10-21 12:40:33 +02:00
20fc48ddf0 [deb] Release v0.14.2 2022-10-20 15:38:45 +02:00
340c031f46 systemd generator: adapt to changed config API
Change-Id: Idb2527b7007aca3a051c1ec9a2d8eecdb55cacef
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/29535
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>
2022-10-20 15:13:29 +02:00
3ac8b4e255 [deb] Release v0.14.1 2022-10-20 14:04:07 +02:00
6ffc73a1e1 Makefile: fix Jenkins host
Change-Id: I1ae3c27d0839b8dffde4cf9da5a13fa00f88a9d3
2022-10-20 14:03:59 +02:00
4ef0b0c01d mlz: avoid error on import due to consistency check
Change-Id: I43751a93b16a0cd9a64ae79da7045fd4e879b065
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/29529
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
2022-10-20 10:39:08 +02:00
1fc805a5a2 gui: clarify needed input for "add sec node" dialog
Change-Id: Ia34eef4df50d545fa779979dd25a239803af0a8e
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/29528
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
2022-10-20 07:54:05 +02:00
05edf98dfe secop_psi.entangle.AnalogInput: fix main value
when the unit of parameter 'value' is taken from tango, the
'$' units of other parameters are already replaced by the configured
value and are not updated. this change fixes this.

not yet tested on entangle, but a test with similar code works

Change-Id: I87ad112b0965b39bb204d6c3d1fc1de6d4e14f60
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/29357
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>
2022-10-19 17:27:29 +02:00
e09c365ea5 [deb] Release v0.14.0 2022-10-19 11:31:51 +02:00
64cb297a06 HasIO: automatic creation of io from uri fails
attached io in HasIO must not be mandatory - either uri or io
has to be given

Change-Id: Id39e40f98020d4051c1ad8105f6af6018aafaea8
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/29349
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>
2022-09-22 15:37:42 +02:00
eaefa1ce87 apply main unit also in structured types
Change-Id: I5a3efb167f2b460b847d8e7ac75a21848976b5f8
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/29350
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>
2022-09-22 14:59:24 +02:00
71aaf7187a improvements on interactive client
- fix handling of exceptions
- add selective logging
- improve formatting of values

Change-Id: I69c11e95aca1cdd222800fd3fd192a6b12b38411
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/29348
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>
2022-09-22 14:59:10 +02:00
0a28192c15 improve general config
for easier configuration of multiple servers on the same machine,
FRAPPY_* env. variables are overriding the values from the
general config file

+ apply expanduser where approporiate

Change-Id: Icb73543402f5fb1b8a248a8b8d7fb470971492f4
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/29351
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>
2022-09-22 14:58:47 +02:00
1b9cac04b1 fix bug when restarting statemachine
- fixed bad if clause
+ better debug message on restart/stop

Change-Id: I4c2327593c014749a32377dac45f0f46c680df2b
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/29352
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>
2022-09-22 14:58:20 +02:00
2b1986ad8f fix bug in persistent.py
- use dirname instead of basename

Change-Id: Id563794a8e5f5c9e4d31750f089eb3b9c3150d94
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/29353
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>
2022-09-22 14:58:05 +02:00
270152d503 improve HasConvergence mixin
- add stop command
- fix bug in cleanup
- reset time spent on write_target

Change-Id: Iaa76cb7a9c6b4a2ccb08313f9880006ab14afe2b
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/29355
Reviewed-by: Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
Tested-by: Markus Zolliker <markus.zolliker@psi.ch>
2022-09-22 14:57:52 +02:00
1922578dfa fix undefined status in softcal
when the status of the rawsensor is not changed, the
status of the calibrated module was not initialized
properly.

Change-Id: I2c23e245226ffb7643060e486c9dfde250a79ce9
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/29356
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>
2022-09-22 14:56:23 +02:00
43880346d6 add simple interactive python client
- SECoP modules are accessible as objects in the main python module
- parameters are accessed as attributes of these objects
- __repr__ is used for listing all parameters
- __call__ is used for 'change target and wait until no more busy'

typically used from a python interpreter or in a jupyter notebook

Change-Id: Idb55684eeff6d1262e5d1517a3ff934f1c1bf208
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/28980
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
2022-08-11 11:15:40 +02:00
d0794a7803 [deb] Release v0.13.1 2022-08-02 15:31:52 +02:00
dc76ac92de secop_mlz: minor rework entangle client
Change-Id: Ie406b4220c22cdbf302a1fd36f2d7407d81a47fa
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/28951
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>
2022-08-02 15:30:53 +02:00
8579368259 make startup faster in case of errors
When the io of one SECoP module fails, it takes ages to startup
because each parameter poll takes the time to wait for a timeout.
After the first communication error on an io, no more startup polls
are tried on the modules using this io.

Change-Id: I0d250953dfe91a7d68d2d2b108395cc25d471afe
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/28588
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>
2022-08-02 11:52:57 +02:00
cd90385e6c an enum with value 0 should be interpreted as False
for example: bool(Enum(off=0, on=1)('off')) is False

Change-Id: Ieb200b4ecf0eed50b657ecc00f73a69810ad828f
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/28586
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>
2022-08-02 11:37:56 +02:00
3acea5f7c7 [deb] Release v0.13.0 2022-08-02 09:47:07 +02:00
c564ae392c default unit to UTF8
Change-Id: Ic958346beb1a3b164c8d7b2826d59cf7e3991e15
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/28946
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Tested-by: Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
Reviewed-by: Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
2022-08-02 09:46:02 +02:00
1357ead435 remove IOHandler stuff
as all code using IO handlers has been changed to use
secop.rwhandler, IO handlers can be removed

Change-Id: Id57fbc4ce2744dbe73bb8792fd45449373f76bb5
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/28526
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>
2022-05-30 09:58:00 +02:00
6a0261c728 allow to convert numpy arrays to ArrayOf
accept all sequences instead of just tuple / list
+ change Module.announceUpdate to convert value before
  comparing with previous one (comparing will not work with numpy arrays)

Change-Id: I5eceef4297607107e2dde688af2833d3651a8775
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/28525
Tested-by: Markus Zolliker <markus.zolliker@psi.ch>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
2022-05-25 14:22:30 +02:00
d717a481d7 feature implementation
implement features including two proposed features
HasOffset and HasLimits

Change-Id: I7949f12dc8abe28fb2ee040e64e7db19d1b23b9a
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/28485
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>
2022-05-25 14:21:21 +02:00
4c94580cb9 channel switcher for Lakeshore 370 with scanner
- add a general channel switcher module
- change ls370res code from IOHandler to rwhandlers
+ fix an issue with the poller when io module is placed below
  using modules in cfg file

after this, IOHandler stuff may be removed from Frappy

Change-Id: I787101fc1e365ae3e0453bfe59291e2011a1fe53
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/28512
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>
2022-05-25 07:47:32 +02:00
8eee7ab3b0 fix keithley 2601b after tests
- add missing super call in initModule
- change mode before writing levels
- fix MEASURE_DCVOLTS instead of MEASURE_VOLTS

Change-Id: Id93187e082db9868f443d4ef8cbdc85acd11be2b
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/28256
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>
2022-04-27 15:46:58 +02:00
478075c545 allow a configfile path as single argument to secop-server
when a full path is given as single argument to secop-server,
the server name has to be sanitized

Change-Id: I1d11f076157548e90877f380f0cab3a6a3f96784
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/28232
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
2022-04-26 08:35:34 +02:00
b7d16d2e16 add 'ts' to the ppms simulation
+ convert from CRLF to LF

Change-Id: I46fab0c970ccc5e7e704a5dc0ab2cfd51213cd31
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/28233
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
2022-04-26 08:35:16 +02:00
d3379d5e95 support for OI mercury series
- temperature (incl. heater)
- pressure (incl. control via valve motor)
- LHe and LN2 levels

not yet included: magnet power supply

Change-Id: Id4ee8f9b7ebfa6284e519ba485217f9a19d70d59
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/28028
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
2022-04-22 16:02:22 +02:00
d6ad5f058d improve poller error handling
- repeated errors on poller are only once logged (per poll
  function / read_* method)
- during exception handling, silent=True on a SECoP error indicates
  that the error is already logged
+ fix the name of HardwareError
+ add test for consistency of SECoPErrors
+ catch socket.timeout in AsynTcp

Change-Id: I9df6c775cc19553b22a4d6e39591092adf7ff9a1
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/28139
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>
2022-04-12 18:12:28 +02:00
a35134978a avoid deadlock in proxy
in secop.proxy the callers modules method announceUpdate is
called from an other thread while the accessLock is locked,
creating a deadlock. solve this by creating an other lock
'updateLock' for the update.

+ add status parameter even to non-Readable proxy modules,
  in order to indicate a failed connection
+ fix an error in secop_psi/softcal.py

Change-Id: Iae7c6d5a74001150a47aa9dc99209c15d972cd5e
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/28130
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>
2022-04-12 18:11:12 +02:00
7891c281e1 fix error in secop.logging
Implement LogfileHandler.getChild. This is needed to inherit
the configured level from the parent handler.

+ remove redundant assignmet of logfile_handler.max_days

Change-Id: I7277c28221bbb6108d75f2437634e9db9bf6761e
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/28140
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>
2022-04-12 18:10:59 +02:00
6460e51920 improved trinamic driver
- safe_current: current limit for unlimited move
- move_limit: max. angle to move with high current > safe_current
- direct axis parameter access is not exported by default
- support for home switch
- allow use without encoder
- automatic reset for motors in a configuration, where the motor
  current is deliberatly low for a limited torque
- improved error message on driving failures

Change-Id: I25f8516905a2c4c3cda09d091d5a43004ec6dc6f
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/28029
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>
2022-04-11 16:25:21 +02:00
b40b0e75b1 motor valve using trinamic motor
This valve needs 8 turns to open. As the encoder forgets
the number if turns on power cycle, a home switch is
mounte, which engages during the last turn when closing.
The final close position is determined by closing the valve
with a defined motor current/torque.

+ fix an issue in StateMachine.start: the first cycle
  must be called after the new state is assigned

Change-Id: I34cd05d10d97b043f9e3126310943b74ee727382
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/28030
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>
2022-04-11 16:25:05 +02:00
af983287e7 use a common poller thread for modules sharing io
When several poller threads are using the same io, the resposivity
of client requests is reduced, as every thread first finishes
its pending communication requests, before it is the turn of the
request thread. This is solved by using one common poller thread
for all modules sharing the same communicator.

+ fix an issue with overriding a property with a parameter, as
  this is the case for pollperiod (cfg was applied to property
  instead of overriding parameter)
+ separate setFastPoll arguments into flag and fast interval
+ fix missing announceUpdate call when read function fails
+ fix mechanism for triggering polls after an io connection
  reconnected again.

Change-Id: I1115a61fae3de80d18416e61f40b52a0eebb637c
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/28021
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
2022-04-06 17:12:36 +02:00
8d23503bbd fix statemachine
- fix: calling state.start(<new state>) on restart must ensure
  that the function <new state> is called before state.start()
  returns.
- modify slighly behaviour of cleanup function

Change-Id: I483a3aefa6af4712b3cf13f62c86d4c06edd1d8d
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/28020
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>
2022-03-29 08:08:44 +02:00
bb097ac3ba reintroduced individual init of generalConfig.defaults
revert basically the former change
"init generalConfig.defaults only in secop-server"

The problem of import order when setting generalConfig.defaults
has to be solved by not overriding already existing keys when
setting the default.

Change-Id: I82121e346607dd74146279c4241e13ab63c14096
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/28011
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>
2022-03-25 07:35:26 +01:00
16a9550080 avoid race conditions in read_*/write_* methods
using one RLock per Module
+ init generalConfig for all tests

Change-Id: I88db6cacdb4aaac2ecd56644ccd6a3e5fd2d1cf2
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/28005
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
2022-03-23 10:40:37 +01:00
9858973ba1 HasConvergence mixin
the HasConvergence mixin runs a state machine to determine
when the value has reached target from parameters 'tolerance',
'settling_time' or detects convergence failure depending on
the parameter 'timeout'.

Change-Id: Iccc3d43bcf5ab54ae02ce3a81423c2decc1b392d
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/27967
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
2022-03-23 09:41:34 +01:00
e6d6179925 init generalConfig.defaults only in secop-server
generalConfig.defaults must not be set on import, as this
depends on import order

Change-Id: I00395b40b4281ddc044c196713f6512068011380
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/27985
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>
2022-03-22 07:56:38 +01:00
60c62a340d support write_ method on readonly param and more
- write method may be used internally on a readonly parameter
+ add IDLE, WARN, BUSY and ERROR to secop.core
+ secop.datatype.EnumType: allow 'self' as member name
+ secop.lib.statemachine: log Restart and Stop exceptions only on debug level
+ secop_psi.ccu4.CCU4: explicit conversion to float
+ secop.proxy: remove superfluos and erroneous make_secop_error

Change-Id: I2f13d31ceacd2bde65eab64f8eae4225556c18f5
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/27963
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>
2022-03-21 12:06:51 +01:00
3c0c60615a fix error in write wrapper and more
- write wrapper must return the result, not the argument
- modify test_modules.py for this
- mixins are not required to inherit from HasAttributes -> modify method check
- config for Attach may be mandatory (default: True)

Change-Id: I34f2965b12d69717e81d9296715467df6f3ac447
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/27934
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>
2022-03-11 15:16:27 +01:00
fda1939324 fix and improved Attached
- Attached checks now for proper base class
- fixes an error in Attached: attached saved in attachedModules
  dict instead on the Attached object (which sits on the class!)
+ fix: in testonly mode errors must be logged before returning
+ fix: use repr of exception in poll to check for repeated errors

Change-Id: I141fa107fed48e58b55ddf1e071987656c0f618f
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/27913
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
2022-03-08 16:34:14 +01:00
8767be2aac improve k2601b driver
- activate current/voltage by setting their target
- deactive output by setting both active parameters to False
- split out power and resistivity to be separate modules

Change-Id: Ie2d7353bcd088da496f547da6fe83a192001fe8f
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/27910
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
2022-03-08 11:25:52 +01:00
39a3e79eb3 move markdown to requirements-dev.txt
Change-Id: I6493483091bffdbff7c5ffec8c52b5b6f48e8664
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/27911
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>
2022-03-08 11:23:21 +01:00
c1d42f0f02 improve softcal
- be more tolerant parsing header of .340 file
- when curve not found, look also in secop_psi/calcurves
- better error message when curve not readable
- check that data points are monotonic
- auto create description if missing
- some more minor stuff

Change-Id: Iecc4dd3dda843b44391aa56272840472a61d4b2c
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/27909
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>
2022-03-07 16:18:21 +01:00
3fe44d32b1 reset connection on identification
required for issue 66.
The other stuff in issue 66 ('error_closed' message), has to be
implemented if and when frappy will support serial server connections

Change-Id: I63bcd59741c3c330a72b829ce8491766ffe6c3a8
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/27908
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>
2022-03-07 16:14:12 +01:00
f58ab263e7 various small fixes
- fix some earlyInit and initModules methods
- remove some comments
- change name of Done unique value to 'Done', this seems more
  useful for __repr__ and debug logging

Change-Id: I73f0e09bfef858ddca11bba0e92e941ebc151160
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/27907
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>
2022-03-07 16:13:13 +01:00
e0fe7e46d1 support for fast poll when busy
Module.setFastPoll may be called depending on status in order to
change the poll interval dependent whether the module is busy or
not. It is assured that the new interval is applied immediately.

Change-Id: I2bd8f68440dc4a93b39e5083a579fc1c123fe578
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/27896
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>
2022-03-04 17:21:03 +01:00
b423235c5d new poll mechanism
- remove secop.poller and basic poller
- regular polls for 'important' parameters done by method doPoll
- all other parameters are polled slower (slowInterval) and
  with lower priority (only one at a time when main poll is due)
- nopoll decorator for read_* to disable poll
- enablePoll attribute (default True) for disabling polling a module
- fast polls may be implemented by means of a statemachine
- configurable slow poll interval
+ allow a Parameter to override a Property (parameter
  Readable.pollinterval overrides Module.pollinterval)

Change-Id: Ib1b3453041a233678b7c4b4add22ac399670e447
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/27832
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>
2022-03-04 09:58:15 +01:00
aa82bc580d proper return value in handler read_* methods
wrapped read_* methods must always return a value

+ do not copy __name__ attribute of handler method to wrapped method

Change-Id: I54cd4b37cf7452621ee734be393aec4611fe809b
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/27870
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>
2022-03-02 11:36:33 +01:00
c2596a9629 implement a state machine
a quite simple, but powerful state machine

There is not need to subclass StateMachine, but use an instance
of it. The code typically lives on methods of an other class.

Features:
- store any variables (except already defined attributes) on the state
- actions handle the conditions to stay or initiate a transition
  by calling the state machines goto method
- a state machine might run endlessly or finish in a None action.
- it may be started or restarted
- a cleanup function for handling exceptions and for stop or restart
- support for time dependent features

Change-Id: I86b86ed1f25d04e305237edb99206912b068aedf
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/27593
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>
2022-02-23 18:20:23 +01:00
bf1761bbc4 common read/write handlers
introduce CommonReadHandler and CommonWriteHandler for
better handling of the case when several parameters are
read or written in one go.

- ppms: use common handlers
+ ppms: modify error handling when command result is not OK
+ store poll attribute on read_* methods

Change-Id: I9a9d0972e206956bcb5a83c204fe5f92c69716e3
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/27822
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>
2022-02-23 17:04:06 +01:00
99588fc815 fix handling commands
- commands have to import arguments and export the result properly

Change-Id: I4ff8879e4e9a93b0a3c57e015b7df8a6328a9bbc
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/27577
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>
2022-02-15 15:38:50 +01:00
bbc4663266 ppms: replace IOHandler by Read/WriteHandler
- add MultiWriteHandler
- the target and value type of secop_psi.ppms.Chamber are enums.
  make the code for them compatible.
+ fix a bug overriding exportname with export=True in parameter

Change-Id: Iec1daf19b3fdf2c017f967e45019867b77c6c59a
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/27583
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>
2022-02-15 15:38:27 +01:00
c1307cdd03 unify name and module on Attached property
- setting the attribute using the name of an attached module
- getting the attribute results in the module object

+ change names iodev to io, iodevClass to ioClass,
  sendRecv to communicate, HasIodev to HasIO

Change-Id: I200b63a5a7dc1453bf6ac998782b065645201900
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/27575
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
2022-01-31 10:57:49 +01:00
b911bc1838 check for problematic value range
A common problematic practice is, to declare the value parameter
with the same FloatRange than the target. Because of measurement
errors, a value might be near, but outside the limit.
In order to avoid this, we force the programmer to declare a
bigger range for the value than for the target, or to
explicitly disable this check on a module property.
It is also fine to declare the value without limits.

This behavior may be disabled via command line option or in the
general config file. For simplicity, FloatRanges inside data
structures are not considered.

+ above command line option is also used to disable the error
  handling on a string to float conversion
+ log appropriate error message for string to float conversion

Change-Id: Ib78ea1fb7c821442bf5847030573c8c27822dea5
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/27574
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>
2022-01-31 09:12:39 +01:00
26a0f2e078 do not convert string to float
a read method should not reply on the automatic conversion
of the return value from string to a number.

- transitional solution with generalConfig.lazy_numer_validation
+ changing slighly generalInit mechanism: for above feature
  generalConfig.init is not required to be called (i.e. when
  used on the client side)

Change-Id: Ibecce1a45669273c105932acdc0908de55bfd1b9
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/27516
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>
2022-01-28 16:04:41 +01:00
4f7083bc98 ReadHandler and WriteHandler decorators
modules with a couple of parameters with similar read_* or
write_* methods may handle them by generic methods wrapped
with decorators ReadHandler / WriteHandler

The trinamic driver is included in this change for demonstrating
how it works.

In a further step, the special handling for the iohandler stuff can
be moved away from secop.server and secop.params, using this feature.

+ fix problem on startup of trinamic driver (needs MultiEvent.queue)
+ some other small fixes
+ apply recommended functools.wraps for wrapping

Change-Id: Ibfeff9209f53c47194628463466cee28366e17ac
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/27460
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>
2022-01-27 17:04:45 +01:00
0909f92e12 UniqueObject
create a class to be used for unique objects.
better for debugging and documentation than just using object()

+ remove unused unique objects

Change-Id: I32f65960ea2fbee4fccbeb49a4e11176b7185aa0
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/27455
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>
2022-01-17 11:38:15 +01:00
f3450375ce enhance logging
- bin/secop-server options -v and -q applied to console logger only
- level for logfile taken from general config
- option for automatic deletion of old logfiles
- added 'comlog' level (between debug and info)

This allows to run the servers by default with 'comlog' level on
the logfiles, which helps a lot for analyzing very rare communication
errors in retrospect.

to avoid spamming of the normal log files, comlog data is stored
separately, one file per communicator

+ redesign of remote logging (no more need of LoggerAdapter)

Change-Id: Ie156a202b1e7304e50bbe830901bc75872f6ffe2
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/27427
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
2022-01-17 10:19:55 +01:00
eb2e8f5f74 fix doc (stringio - > io)
+ bug in secop.lib.classdoc

Change-Id: Ic1f6f2896466ce53dd176a338088b7ee6b8047af
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/27428
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>
2022-01-13 10:23:38 +01:00
2ef0da68e8 change name of read_hw_status method in sequencer mixin
change name to 'readHwStatus'
needed after commit "check for bad read* and write_* methods"

Change-Id: I27467aa2a3a3bb0db5f418c99f2d2065390a190a
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/27394
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>
2022-01-11 10:10:11 +01:00
9b38db7706 check for bad read_* and write_* methods
raise a ProgrammingError when a read_<param> or write_<param>
method is defined, but <param> is no parameter.

Change-Id: Iae4e617d078229182a90b202a6f81ebc49050118
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/27386
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>
2022-01-07 17:44:49 +01:00
8f7fb1e45b improve handling of module init methods
- complain when super call is omitted (this is a common programming
  error in Mixins)
- redesign waiting mechanism for startup

+ rename MultiEvent method 'setfunc' to 'get_trigger'

Change-Id: Ica27a75597321f2571a604a7a55448cffb1bec5e
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/27369
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>
2022-01-07 17:41:59 +01:00
f13e29aad2 introduce general config file
+ redesign general config
+ remove obsolete secop/paths.py

Change-Id: Ice08ec37c54b1a6e2e2e6e29fdaaf0bd2dd725dc
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/27362
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
2022-01-05 09:05:00 +01:00
c5d228ffc4 add timeouts to MultiEvents
to be used for a follow up change for startup events

Change-Id: Id8816eb8f561dcd8d1473e25a9685e796fb14953
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/27364
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
2021-12-22 15:12:56 +01:00
071ba38b60 remote logging (issue 46)
Change-Id: Id92e66280811b1f871157b5c2ceca65085d2c15b
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/27346
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>
2021-12-21 09:06:22 +01:00
b29b1e1b36 entangle.AnalogOutput: fix window mechanism
fix 2 problems:
- in case the window is smaller than the pollinterval, isAtTarget might be False
  for up to the timeout delay
- in case the history is shorter than the window, and the ramp is fast enough to miss
  any points during ramp, isAtTarget is True before the window time is reached.
  This happens because the history is reset on write_target

+ do not wait when target is not changed (by more than precision)

tested!

Change-Id: Ia4ff4378fe91fa93be50168b2883a20b49ebfb6a
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/27159
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
Reviewed-by: Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
2021-11-17 16:55:43 +01:00
c91d726f9d add more tests and fixes for command inheritance
- fix CommandType.__repr__
- secop/modules.py: command properties are allowed to be configured:
  - section 2: remove comment and rename
  - section 3: all accessible properties should be checked
- command description should be inherited also when taken from docstring
- move test for command inheritance to test_modules.py
- added tests to check for valid properties of commands

Change-Id: Ic7795e305048625558e415ece099e6824df6e2c4
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/27135
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>
2021-11-12 16:48:58 +01:00
1d75d192e5 automatic saving of persistent parameters
change persistent flag to be an enum off/on/auto

Change-Id: I3b1685ea76afb3b7f8c2e6ca63fbf81fa987750e
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/27100
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>
2021-11-12 10:48:10 +01:00
4bf3acab98 various small changes
- set default target unit to '$'
- shorten too verbose error message on client
- add shutdown method to server and dispatcher

Change-Id: Ib3a8b26bc31e988e45a3ff2efd734168d723d794
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/27095
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>
2021-11-12 10:47:52 +01:00
eebc9232cd debian: fix email addresses in changelog
Change-Id: I7279f2eb0fecbde5f7f02a983e1b58537b79566f
2021-11-12 09:18:24 +01:00
c16f159599 [deb] Release v0.12.4 2021-11-11 16:21:19 +01:00
796be752b7 fix command inheritance
Command.ownProperties must be definead in __init__

+ add test for this

Change-Id: I283331be6439a49ec61d28f04869a5b44704236f
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/27104
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>
2021-11-11 14:20:10 +01:00
b8e8d24b50 [deb] Release v0.12.3 2021-11-10 16:33:19 +01:00
45dd14a72c fix feature for removing commands
from a bug introduced in the 'fix parameter inheritance' change

Change-Id: I757c354130077d8838aac864b21b4f81caa9bccf
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/27096
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>
2021-11-10 16:21:58 +01:00
ad7cfe4ea0 automatically register subclasses of AsynConn
using __init_subclass__ method.

+ correct typo

Change-Id: I9a57c467efcd138651248f92fbf84195624e0b9a
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/27093
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>
2021-11-10 13:50:46 +01:00
47d09e9b08 improve simulation
- customizable simulation interval
- add stop command to SimDrivable

Change-Id: Id4eb0ec465ecc97a115397c295c4a052aceb762c
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/27092
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>
2021-11-10 13:50:30 +01:00
d76d79aebb omit updates of unchanged values within short time
Sometimes it happens, that the same value determined once is
assigned several times to a parameter within a very short period.
Sending multiple updates is not useful in this case.

Change-Id: Icea66934c831fd9b2eac7d0394a124d002469914
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/27091
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
2021-11-10 12:34:51 +01:00
a64eb7f33b fix python 3.5 compatibility
- move workaround for PEP487 ty secop.lib.py35compat
- add missing context manager to TCPServer
- remove redundant code in secop/property.py

Change-Id: I0822010f196ad6cec5ec44e990013b79c5d4048b
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/27090
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>
2021-11-10 12:34:31 +01:00
3d0d779d81 fix property inheritance
+ remove obsolete filterDescriptor method
+ copying properties should support extensions (e.g. Attached)

Change-Id: I301947a4ae28fcad98250b277c6b0e7e0100eaf9
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/27084
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>
2021-11-09 18:21:03 +01:00
3b7cc33f64 fix parameter inheritance
Correct inheritance has to follow the MRO, not only consider
the direct base classes.

Patchset 3: changed only tests, indicating that we need to change the code

Following patchsets include a major change in params.py and
modules.py. The parameter properties for inheritance, corresponding
mainly to the constructor arguments have to be stored separately
from the property values including inherited stuff.

Change-Id: Ibcbccb6abcc22e7e2d91df8f70ef64226684d8cc
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/26805
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>
2021-11-09 15:06:36 +01:00
41489a4a24 show first instead of last traceback on multiple errors
on initialization, the error message are collected and
shown before starting the server together with the traceback
of the last error. This should be the traceback of the
first error instead.

Change-Id: I86d4b42f7d4f98f2ab3b692cd6548e62acffa11b
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/27011
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>
2021-11-09 14:42:14 +01:00
3140d454ae fix Parameter/Command copy method
in case the Parameter/Command is subclassed

Change-Id: Ib34fc78e72cd04e743e35ef7ccd40b2eae03b614
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/26450
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
2021-07-27 08:06:20 +02:00
7bd166a8a1 persistent params / trinamic motor
for the trinamic motor we need persistent parameters

Change-Id: Id509b87f8368ea5ba1aca71951f79433b0b4b79f
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/26405
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
2021-07-27 07:57:49 +02:00
f3978385b4 GUI fixes
- do not show command result dialog when result is None
- apply fmtstr, if available

+ fix io import in secop.core
+ change old style <basecls>.__init__(self, ...) calls to super().__init__(...)

Change-Id: I599d5d8e8ff430ea9454a0858d703290e87454fc
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/26397
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Jens Krueger <jens.krueger@frm2.tum.de>
Reviewed-by: Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
2021-07-16 14:50:00 +02:00
bc6a99e11b introduce BytesIO
rename secop.stringio to secop.io, which includes now
also BytesIO and the common base class IOBase

+ a small fix in error handling

Change-Id: I8e305e2c164f4ed131f4b36ef45edd8bd222336d
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/26393
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>
2021-07-15 15:54:51 +02:00
6b610f1e25 remove irrelevant comments
+ improve error message 'can not convert %r to float'
Change-Id: Idf534a4105086463fd238d7c91317424a809d7ba

Change-Id: I09260dda8deff1e6ee0af2fa38a42a09884a2061
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/26345
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
2021-07-06 16:13:33 +02:00
9a60de9c1c various fixes
- nodestatechange callback must appear after the online attribute
  is changed
- IntRange: move range validation outside of try except
- fixes on some error names
- well defined error message 'no such class' in secop.lib.get_class
- try again 4 times when starting Tcp Server on EADDRINUSE
- fix error handling

Change-Id: I4eee9b79ea8173936b9f5193b87e006ac8fca827
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/26171
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>
2021-06-14 09:03:41 +02:00
026e657799 Makefile: fix docker image
Change-Id: I90a3fe36abbea0501c7e7e27fa33e90bfd4f116d
2021-05-29 14:46:25 +02:00
35f08bf4ad [deb] Release v0.12.2 2021-05-18 10:29:17 +02:00
76ae75a926 secop_mlz: small fixes
- wrong unit for 'ramp'
- disable Setposition for TemperatureController
- write_target should return the new target
- AnalogInput should not crash initialisation if reading the unit fails
- convert super(...) calls to py3 style super()
- use proper exception chaining
- NamedDigital*put: mapping may already be a dict.

Change-Id: I03ce5f29581dcb3b33466771e7a8b8dd4b1e2bdb
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/25960
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Tested-by: Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
Reviewed-by: Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
2021-05-18 10:27:54 +02:00
27ac70b1da allow to remove accessibles
removing an inherited command or parameter can now be indicated
with an attribute set to None

Change-Id: I7582434013856190b346e381d2e634509896ccb3
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/25963
Tested-by: Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
2021-05-18 10:27:37 +02:00
a343b07f1d treat specifier of describe message
for debugging purposes, getting the description of a module
or even a parameter is usefull

therefore the following will work

describe <module>
describe <module>:<param>

Change-Id: Ie262ae12c23d1c151cdc01830ad4f8fd5ec3c5f1
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/25962
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>
2021-05-17 17:36:47 +02:00
09411f36f3 fix issue with new syntax in simulation
for creating extra parameters a subclass of SimBase is created,
in order to treat parameters and read_/write_ methods properly.

Change-Id: I9061b9afb0f8922b36b8f9448c45bb3aadb8f515
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/25961
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>
2021-05-17 17:36:28 +02:00
b0f0a48e51 [deb] Release v0.12.1 2021-05-04 09:42:53 +02:00
e4261ecfe1 remove secop-console from debian *.install file
Change-Id: I9ba8bd37b9460db83512a35bc2e98b63a4df9687
2021-05-04 09:41:12 +02:00
598dd07888 [deb] Release v0.12.0 2021-05-04 08:49:58 +02:00
f2b330a3f0 Correct checks enum
Change-Id: Ibe23825bfdfcac15262987383407e18ead4cf880
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/25840
Tested-by: Bjoern Pedersen <bjoern.pedersen@frm2.tum.de>
Reviewed-by: Bjoern Pedersen <bjoern.pedersen@frm2.tum.de>
2021-04-30 08:08:22 +02:00
a85201ad7d Another Jenkisfile error
Change-Id: I313f1471aad166c794374c7d381c1b66a4395b4d
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/25839
Tested-by: Bjoern Pedersen <bjoern.pedersen@frm2.tum.de>
Reviewed-by: Bjoern Pedersen <bjoern.pedersen@frm2.tum.de>
2021-04-30 07:59:02 +02:00
2cf6e167a8 No pull for images, they are recreated in the job
Change-Id: I4ac389873310a411ea33ed6c06af8ec8f752dfd6
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/25838
Tested-by: Bjoern Pedersen <bjoern.pedersen@frm2.tum.de>
Reviewed-by: Bjoern Pedersen <bjoern.pedersen@frm2.tum.de>
2021-04-30 07:54:43 +02:00
ffc2c495fb Fixes to Jenkinsfile
Change-Id: Id75b49fd8c38ef90a0869ba6845a36f338a239b5
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/25837
Tested-by: Bjoern Pedersen <bjoern.pedersen@frm2.tum.de>
Reviewed-by: Bjoern Pedersen <bjoern.pedersen@frm2.tum.de>
2021-04-30 07:52:14 +02:00
7ea4e3955a Jenkisfile: verification
Change-Id: I58829f1672c95bb76c765aa1ce86d808f9e50dfd
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/25836
Tested-by: Bjoern Pedersen <bjoern.pedersen@frm2.tum.de>
Reviewed-by: Bjoern Pedersen <bjoern.pedersen@frm2.tum.de>
2021-04-30 07:49:28 +02:00
cc1632e07d user friendly reporting of config errors
Config errors are collected first, and raised after processing
all modules. This is more user friendly.

+ remove redundant check for predefined accessibles in modules.py
+ fixed error handling for exporting parameters in params.py
+ fixed handling of bare attributes overwriting properties
+ fixed race condition in writeInitParams

Change-Id: I894bda291ab85ccec3d771c4903393c808af0a2a
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/25128
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>
2021-04-27 18:20:53 +02:00
1e17d0c6b9 fixed bugs from syntax migration
- a new wrapper for a read function is not only to be created when
  the a new read method is in the class dict, but also when
  it is inherited, but not yet wrapped
- a handler must not be ignored, when a write method is inherited
- a proxy class must not call checkProperties

+ remove trailing spaces in tutorial_helevel.rst

Change-Id: I16024c14232ea200db91a1bc07ec23326219ab68
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/25093
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
2021-03-05 08:33:23 +01:00
e568c665a8 improve tutorial_helevel
- fix indent of query method
- add remark about the CCU4 cid command

Change-Id: Iaee821cf7739536cc823494f646a1d2a4bbbfa44
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/25092
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
2021-03-03 16:03:26 +01:00
d0f895ed44 fix autoscan behaviour in ls370res
when in autoscan, and dwell < filter, the channel was never read

+ do not export pollinterval on both Main and ResChannel

Change-Id: I50df9e151b219ab28875ac78107dcdfdede42c51
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/25087
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
2021-03-03 14:31:40 +01:00
69f5692951 move historywriter to secop_psi
as historywriter currently could be used at psi only
secop_psi is a better place for it

+ add comment about a general config file

Change-Id: I9b0e74d3da83ac485bd4bcc13475695c3140822c
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/25077
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>
2021-03-02 16:00:26 +01:00
980499ba41 fixed errors during migration
- reference.rst did still contain do_<something>
- trailing commas in secop_demo/cryo.py

Change-Id: I5bb8bd310576366c8cfe406f0ec770fa40bcab5b
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/25079
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>
2021-03-01 16:33:08 +01:00
6cde8177d5 added hook for optional history writer
- activated with envrionment variable FRAPPY_HISTORY
- using the (currently) private package 'frappyhistory'

Change-Id: I8f747b29d8311af677ed77268a4c38c8d71b08c2
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/25064
Tested-by: Markus Zolliker <markus.zolliker@psi.ch>
Reviewed-by: Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
2021-02-26 13:26:50 +01:00
23779c8f8c lookup cfg files in a list of directories
environment variable SECOP_CONFDIR may be a ':' separated
list of directories to lookup for cfg files

Change-Id: I058be6a270d3a3f6cd8ca45fdd4ab68c80fa5c23
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/25063
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>
2021-02-26 13:22:19 +01:00
899a07aec8 remove obsolete code
- basic_validators is not needed any more since the implementation
  of datatypes.Stub
- client/baseclient.y is replaced by client/__init__.py both for
  the gui client and NICOS SECoP client
- lib/parsing.py used by baseclient only

Change-Id: I15b6ac880017000e155b8f6b7e2456e1bbf56dab
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/25058
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>
2021-02-26 13:21:48 +01:00
6a32ecf342 fix inheritance order
+ hide pollperiod on PPMS Modules

Change-Id: I77ad5502884360bf6babfd226de0675ee06a6196
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/25054
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>
2021-02-24 15:42:33 +01:00
1464a6bce5 try to follow PEP8
- fixed most important code after checking with flake8
- ignored code which has to be reworked or removed
+ mark unused code with 'TODO: remove ...'

Change-Id: Ic45e541049e391e2853d29cd64bb0963bd9a2125
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/25053
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
2021-02-24 15:00:06 +01:00
bb6f692c6b after running isort
Change-Id: I6d7dbb8dee9480fc9242529089a1b40f17f068e7
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/25052
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
2021-02-24 10:50:37 +01:00
1a8ddbc696 removed old style syntax
- removed secop/metaclass.py
- moved code from ModuleMeta to modules.HasAccessibles.__init_subclass__
- reworked properties:
  assignment obj.property = value now always allowed
- reworked Parameters and Command to be true descriptors
- Command must now be solely used as decorator
- renamed 'usercommand' to 'Command'
- command methods no longer start with 'do_'
- reworked mechanism to determine accessible order:
  the attribute paramOrder, if given, determines order of accessibles
+ fixed some issues makeing the IDE more happy
+ simplified code for StatusType and added a test for it

Change-Id: I8045cf38ee6f4d4862428272df0b12a7c8abaca7
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/25049
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>
2021-02-24 08:44:13 +01:00
ed02131a37 enhance documentation
- flatten hierarchy (some links do not work when using folders)
- add a tutorial for programming a simple driver
- clean description using inspect.cleandoc
+ fix a bug with 'unit' pseudo property in a Parameter used as override

Change-Id: I31ddba5d516d1ee5e785e28fbd79fca44ed23f5e
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/25000
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
2021-02-10 17:07:08 +01:00
a19425684c new syntax for parameter/commands/properties
New Syntax:

- define properties and parameters as class attributes directly
  instead of items in class attribute dicts
- define commands with decorator @usercommand(...)
- old syntax is still supported for now

still to do (with decreasing priority):
- turn parameters into descriptors (vs. creating getters/setters)
- migrate all existing code to new syntax
- get rid of or reduce code in metaclasses using __set_name__ and
  __init_subclass__ instead, including a fix for allowing py < 3.6

Change-Id: Id47e0f89c506f50c40fa518b01822c6e5bbf4e98
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/24991
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>
2021-02-05 12:30:13 +01:00
24cffad4df make arguments of Parameter and Override consistent
- allow (description and) datatype being positional args in Override
- disallow ctr and unit being a positional arg in Parameter
- disallow reorder being a positional arg in Override

Change-Id: Ic5711d091af11d5843943b0b2b31567127f8ed8c
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/24934
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>
2021-01-27 10:08:53 +01:00
05fec236da improve softcal
- bugfix: stop searching for file on the first match
- allow to ignore the sign on input by abs (bool) parameter

Change-Id: I0e5544d7645f124d4c4ac720174b0f5bde7e71a6
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/24928
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
2021-01-27 08:56:26 +01:00
b1a88440ef main module of LS370 is now drivable
The main value of main module is the selected channel, it is 0
when pausing during scanning, and the status is busy.

+ cosmetics to make IDE more happy

Change-Id: I11d8f08ea67d25fb00f7492080b4a55efc124bfb
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/24927
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>
2021-01-27 08:56:04 +01:00
c9721649a3 make order of accessibles work again
- when applying overrides with reorder=True, take ctr from Override,
  else copy from the cloned Accesible. This did not work properly
- reworked:
  - replaced CountedObj class by object_counter
  - accessibles created by a copy or by applying Overrides
    do not need fresh counted values
- adjusted tests

Change-Id: Id2fcf1ab1295aa1ea80ea81ae8cd02d36f86e969
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/24926
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>
2021-01-27 08:55:30 +01:00
d2c3370a40 fix initwrite behaviour
with handlers, a parameter from the cfg file which is not the
first of parameters with the same handler were not written.
fix: write_<param> method is called for all parameters in
<module>.writeDict even if there is no poll entry.

with this fix, when a parameter has the property initwrite=True,
the write_<param> method is called even when <param>
is not polled and even when <module>.pollerClass is None

Change-Id: I9b397deb5b20709fc4fa7c860c85b251a204c7f6
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/23995
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>
2020-09-29 07:52:59 +02:00
51 changed files with 1506 additions and 2918 deletions

View File

@ -1,22 +0,0 @@
import sys
import os
from glob import glob
from frappy_psi.calcurve import CalCurve
os.chdir('/Users/zolliker/gitpsi/calcurves')
if len(sys.argv) > 1:
calib = sys.argv[1]
c = CalCurve(calib)
else:
for file in sorted(glob('*.*')):
if file.endswith('.md') or file.endswith('.std'):
continue
try:
c = CalCurve(file)
xy = c.export()
print('%9.4g %12.7g %9.4g %9.4g %s' % (tuple(c.extx) + tuple(c.exty) + (file,)))
except Exception as e:
print(file, e)
calib = file

View File

@ -6,7 +6,7 @@ Node('QnwTC1test.psi.ch',
Mod('io',
'frappy_psi.qnw.QnwIO',
'connection for Quantum northwest',
uri='tcp://ldmcc01-ts:3004',
uri='tcp://ldm-fi-ts:3001',
)
Mod('T',

View File

@ -6,7 +6,7 @@ Node('TFA10.psi.ch',
Mod('io',
'frappy_psi.thermofisher.ThermFishIO',
'connection for ThermoFisher A10',
uri='tcp://ldmse-d910-ts:3001',
uri='tcp://ldm-fi-ts:3002',
)
Mod('T',

View File

@ -24,7 +24,6 @@ Mod('ts_low',
minrange=13,
range=22,
tolerance = 0.1,
vexc = 3,
htrrng=4,
)
@ -33,8 +32,7 @@ Mod('ts_high',
'sample Cernox',
channel = 1,
switcher = 'lsc_channel',
minrange=11,
vexc = 5,
minrange=9,
range=22,
tolerance = 0.1,
htrrng=5,
@ -47,8 +45,6 @@ Mod('ts',
value=Param(unit='K'),
low='ts_low',
high='ts_high',
#min_high=0.6035,
#max_low=1.6965,
min_high=0.6,
max_low=1.7,
tolerance=0.1,

View File

@ -1,19 +0,0 @@
Node('attocube_test.psi.ch',
'a single attocube axis',
interface='tcp://5000',
)
Mod('r',
'frappy_psi.attocube.Axis',
'ANRv220-F3-02882',
axis = 1,
value = Param(unit='deg'),
tolerance = 0.1,
target_min = 0,
target_max = 360,
steps_fwd = 45,
steps_bwd = 85,
step_mode = True,
# gear = 1.2,
)

View File

@ -1,60 +0,0 @@
Node('flowsas.psi.ch',
'flowsas test motors',
'tcp://3000',
)
#Mod('mot_io',
# 'frappy_psi.phytron.PhytronIO',
# 'io for motor control',
# uri = 'serial:///dev/ttyUSB0',
# )
#Mod('hmot',
# 'frappy_psi.phytron.Motor',
# 'horizontal axis',
# axis = 'X',
# io = 'mot_io',
# encoder_mode = 'NO',
# )
#Mod('vmot',
# 'frappy_psi.phytron.Motor',
# 'vertical axis',
# axis = 'Y',
# io = 'mot_io',
# encoder_mode= 'NO',
# )
Mod('syr_io',
'frappy_psi.cetoni_pump.LabCannBus',
'Module for bus',
deviceconfig = "/home/l_samenv/frappy/cetoniSDK/CETONI_SDK_Raspi_64bit_v20220627/config/conti_flow",
)
Mod('syr1',
'frappy_psi.cetoni_pump.SyringePump',
'First syringe pump',
io='syr_io',
pump_name = "Nemesys_S_1_Pump",
valve_name = "Nemesys_S_1_Valve",
inner_diameter_set = 14.5673,
piston_stroke_set = 60,
)
Mod('syr2',
'frappy_psi.cetoni_pump.SyringePump',
'Second syringe pump',
io='syr_io',
pump_name = "Nemesys_S_2_Pump",
valve_name = "Nemesys_S_2_Valve",
inner_diameter_set = 14.5673,
piston_stroke_set = 60,
)
Mod('contiflow',
'frappy_psi.cetoni_pump.ContiFlowPump',
'Continuous flow pump',
io='syr_io',
inner_diameter_set = 14.5673,
piston_stroke_set = 60,
)

16
cfg/lockin830_cfg.py Normal file
View File

@ -0,0 +1,16 @@
Node('lockin830test.psi.ch',
'lockin830 test',
'tcp://5000',
)
Mod('io',
'frappy_psi.SR830.SR830_IO',
'lockin communication',
uri='tcp://linse-976d-ts:3002',
)
Mod('XY',
'frappy_psi.SR830.XY',
'XY channels',
io='io',
)

34
cfg/multimeter_cfg.py Normal file
View File

@ -0,0 +1,34 @@
Node('multimetertest.psi.ch',
'multimeter test',
'tcp://5000',
)
Mod('io',
'frappy_psi.HP.HP_IO',
'multimeter communication',
uri='/dev/cu.usbserial-21410',
)
Mod('Voltage',
'frappy_psi.HP.Voltage',
'voltage',
io='io',
)
Mod('Current',
'frappy_psi.HP.Current',
'current',
io='io',
)
Mod('Resistance',
'frappy_psi.HP.Resistance',
'resistivity',
io='io',
)
Mod('Frequency',
'frappy_psi.HP.Frequency',
'resistivity',
io='io',
)

View File

@ -1,12 +0,0 @@
Node('flowsas.psi.ch',
'peristaltic pump',
'tcp://3000',
)
Mod('peripump',
'frappy_psi.gilsonpump.PeristalticPump',
'Peristaltic pump',
addr_AO = 'ao1',
addr_dir_relay = 'o1',
addr_run_relay = 'o2',
)

View File

@ -1,13 +0,0 @@
Node('vf.psi.ch',
'small vacuum furnace',
'tcp://5000',
)
Mod('p',
'frappy_psi.ionopimax.VoltageInput',
'Vacuum pressure',
addr = 'av2',
rawrange = (0, 10),
valuerange = (0, 10),
value = Param(unit='V'),
)

View File

@ -1,11 +0,0 @@
Node('flowsas.psi.ch',
'rheometer triggering',
'tcp://3000',
)
Mod('rheo',
'frappy_psi.rheo_trigger.RheoTrigger',
'Trigger for the rheometer',
addr='dt1',
doBeep = False,
)

67
cfg/sim921_cfg.py Normal file
View File

@ -0,0 +1,67 @@
Node('bridge.psi.ch',
'ac resistance bridge',
'tcp://5000',
)
Mod('io',
'frappy_psi.bridge.BridgeIO',
'communication to sim900',
uri='serial:///dev/cu.usbserial-14340',
)
Mod('res1',
'frappy_psi.bridge.Resistance',
'module communication',
io='io',
port=1,
)
Mod('res2',
'frappy_psi.bridge.Resistance',
'module communication',
io='io',
port=3,
)
Mod('res3',
'frappy_psi.bridge.Resistance',
'module communication',
io='io',
port=5,
)
Mod('phase1',
'frappy_psi.bridge.Phase',
'module communication',
resistance='res1',
)
Mod('phase2',
'frappy_psi.bridge.Phase',
'module communication',
resistance='res2',
)
Mod('phase3',
'frappy_psi.bridge.Phase',
'module communication',
resistance='res3',
)
Mod('dev1',
'frappy_psi.bridge.Deviation',
'module communication',
resistance='res1',
)
Mod('dev2',
'frappy_psi.bridge.Deviation',
'module communication',
resistance='res1',
)
Mod('dev3',
'frappy_psi.bridge.Deviation',
'module communication',
resistance='res3',
)

View File

@ -138,6 +138,13 @@ Mod('T_one_K',
io='itc',
)
Mod('htr_one_K',
'frappy_psi.mercury.HeaterOutput',
'1 K plate warmup heater',
slot='DB3.H1',
io='itc',
)
Mod('T_mix_wup',
'frappy_psi.mercury.TemperatureLoop',
'mix. chamber warmup temperature',

View File

@ -232,7 +232,7 @@ class ReadFailedError(SECoPError):
class OutOfRangeError(SECoPError):
"""The value read from the hardware is out of sensor or calibration range"""
"""The requested parameter can not be read just now"""
name = 'OutOfRange'

View File

@ -1,304 +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:
# Markus Zolliker <markus.zolliker@psi.ch>
#
# *****************************************************************************
"""extended parameters
special parameter classes with some automatic functionality
"""
import re
from frappy.core import Parameter, Property
from frappy.datatypes import BoolType, DataType, DataTypeType, EnumType, \
FloatRange, StringType, StructOf, ValueType
from frappy.errors import ProgrammingError
class StructParam(Parameter):
"""convenience class to create a struct Parameter together with individual params
Usage:
class Controller(Drivable):
...
ctrlpars = StructParam('ctrlpars struct', [
('pid_p', 'p', Parameter('control parameter p', FloatRange())),
('pid_i', 'i', Parameter('control parameter i', FloatRange())),
('pid_d', 'd', Parameter('control parameter d', FloatRange())),
], readonly=False)
...
then implement either read_ctrlpars and write_ctrlpars or
read_pid_p, read_pid_i, read_pid_d, write_pid_p, write_pid_i and write_pid_d
the methods not implemented will be created automatically
"""
# use properties, as simple attributes are not considered on copy()
paramdict = Property('dict <parametername> of Parameter(...)', ValueType())
hasStructRW = Property('has a read_<struct param> or write_<struct param> method',
BoolType(), default=False)
insideRW = 0 # counter for avoiding multiple superfluous updates
def __init__(self, description=None, paramdict=None, prefix_or_map='', *, datatype=None, readonly=False, **kwds):
"""create a struct parameter together with individual parameters
in addition to normal Parameter arguments:
:param paramdict: dict <member name> of Parameter(...)
:param prefix_or_map: either a prefix for the parameter name to add to the member name
or a dict <member name> or <parameter name>
"""
if isinstance(paramdict, DataType):
raise ProgrammingError('second argument must be a dict of Param')
if datatype is None and paramdict is not None: # omit the following on Parameter.copy()
if isinstance(prefix_or_map, str):
prefix_or_map = {m: prefix_or_map + m for m in paramdict}
for membername, param in paramdict.items():
param.name = prefix_or_map[membername]
datatype = StructOf(**{m: p.datatype for m, p in paramdict.items()})
kwds['influences'] = [p.name for p in paramdict.values()]
self.updateEnable = {}
if paramdict:
kwds['paramdict'] = paramdict
super().__init__(description, datatype, readonly=readonly, **kwds)
def __set_name__(self, owner, name):
# names of access methods of structed param (e.g. ctrlpars)
struct_read_name = f'read_{name}' # e.g. 'read_ctrlpars'
struct_write_name = f'write_{name}' # e.h. 'write_ctrlpars'
self.hasStructRW = hasattr(owner, struct_read_name) or hasattr(owner, struct_write_name)
for membername, param in self.paramdict.items():
pname = param.name
changes = {
'readonly': self.readonly,
'influences': set(param.influences) | {name},
}
param.ownProperties.update(changes)
param.init(changes)
setattr(owner, pname, param)
param.__set_name__(owner, param.name)
if self.hasStructRW:
rname = f'read_{pname}'
if not hasattr(owner, rname):
def rfunc(self, membername=membername, struct_read_name=struct_read_name):
return getattr(self, struct_read_name)()[membername]
rfunc.poll = False # read_<struct param> is polled only
setattr(owner, rname, rfunc)
if not self.readonly:
wname = f'write_{pname}'
if not hasattr(owner, wname):
def wfunc(self, value, membername=membername,
name=name, rname=rname, struct_write_name=struct_write_name):
valuedict = dict(getattr(self, name))
valuedict[membername] = value
getattr(self, struct_write_name)(valuedict)
return getattr(self, rname)()
setattr(owner, wname, wfunc)
if not self.hasStructRW:
if not hasattr(owner, struct_read_name):
def struct_read_func(self, name=name, flist=tuple(
(m, f'read_{p.name}') for m, p in self.paramdict.items())):
pobj = self.parameters[name]
# disable updates generated from the callbacks of individual params
pobj.insideRW += 1 # guarded by self.accessLock
try:
return {m: getattr(self, f)() for m, f in flist}
finally:
pobj.insideRW -= 1
setattr(owner, struct_read_name, struct_read_func)
if not (self.readonly or hasattr(owner, struct_write_name)):
def struct_write_func(self, value, name=name, funclist=tuple(
(m, f'write_{p.name}') for m, p in self.paramdict.items())):
pobj = self.parameters[name]
pobj.insideRW += 1 # guarded by self.accessLock
try:
return {m: getattr(self, f)(value[m]) for m, f in funclist}
finally:
pobj.insideRW -= 1
setattr(owner, struct_write_name, struct_write_func)
super().__set_name__(owner, name)
def finish(self, modobj=None):
"""register callbacks for consistency"""
super().finish(modobj)
if modobj:
if self.hasStructRW:
def cb(value, modobj=modobj, structparam=self):
for membername, param in structparam.paramdict.items():
setattr(modobj, param.name, value[membername])
modobj.addCallback(self.name, cb)
else:
for membername, param in self.paramdict.items():
def cb(value, modobj=modobj, structparam=self, membername=membername):
if not structparam.insideRW:
prev = dict(getattr(modobj, structparam.name))
prev[membername] = value
setattr(modobj, structparam.name, prev)
modobj.addCallback(param.name, cb)
class FloatEnumParam(Parameter):
"""combine enum and float parameter
Example Usage:
vrange = FloatEnumParam('sensor range', ['500uV', '20mV', '1V'], 'V')
The following will be created automatically:
- the parameter vrange will get a datatype FloatRange(5e-4, 1, unit='V')
- an additional parameter `vrange_idx` will be created with an enum type
{'500uV': 0, '20mV': 1, '1V': 2}
- the method `write_vrange` will be created automatically
However, the methods `write_vrange_idx` and `read_vrange_idx`, if needed,
have to implemented by the programmer.
Writing to the float parameter involves 'rounding' to the closest allowed value.
Customization:
The individual labels might be customized by defining them as a tuple
(<index>, <label>, <float value>) where either the index or the float value
may be omitted.
When the index is omitted, the element will be the previous index + 1 or
0 when it is the first element.
Omitted values will be determined from the label, assuming that they use
one of the predefined unit prefixes together with the given unit.
The name of the index parameter is by default '<name>_idx' but might be
changed with the idx_name argument.
"""
# use properties, as simple attributes are not considered on copy()
idx_name = Property('name of attached index parameter', StringType(), default='')
valuedict = Property('dict <index> of <value>', ValueType(dict))
enumtype = Property('dict <label> of <index', DataTypeType())
# TODO: factor out unit handling, at the latest when needed elsewhere
PREFIXES = {'q': -30, 'r': -27, 'y': -24, 'z': -21, 'a': -18, 'f': -15,
'p': -12, 'n': -9, 'u': -6, 'µ': -6, 'm': -3,
'': 0, 'k': 3, 'M': 6, 'G': 9, 'T': 12,
'P': 15, 'E': 18, 'Z': 21, 'Y': 24, 'R': 25, 'Q': 30}
def __init__(self, description=None, labels=None, unit='',
*, datatype=None, readonly=False, **kwds):
if labels is None:
# called on Parameter.copy()
super().__init__(description, datatype, readonly=readonly, **kwds)
return
if isinstance(labels, DataType):
raise ProgrammingError('second argument must be a list of labels, not a datatype')
nextidx = 0
try:
edict = {}
vdict = {}
for elem in labels:
if isinstance(elem, str):
idx, label = [nextidx, elem]
else:
if isinstance(elem[0], str):
elem = [nextidx] + list(elem)
idx, label, *tail = elem
if tail:
vdict[idx], = tail
edict[label] = idx
nextidx = idx + 1
except (ValueError, TypeError) as e:
raise ProgrammingError('labels must be a list of labels or tuples '
'([index], label, [value])') from e
pat = re.compile(rf'([+-]?\d*\.?\d*) *({"|".join(self.PREFIXES)}){unit}$')
try:
# determine missing values from labels
for label, idx in edict.items():
if idx not in vdict:
value, prefix = pat.match(label).groups()
vdict[idx] = float(f'{value}e{self.PREFIXES[prefix]}')
except (AttributeError, ValueError) as e:
raise ProgrammingError(f"{label!r} has not the form '<float><prefix>{unit}'") from e
try:
enumtype = EnumType(**edict)
except TypeError as e:
raise ProgrammingError(str(e)) from e
datatype = FloatRange(min(vdict.values()), max(vdict.values()), unit=unit)
super().__init__(description, datatype, enumtype=enumtype, valuedict=vdict,
readonly=readonly, **kwds)
def __set_name__(self, owner, name):
super().__set_name__(owner, name)
if not self.idx_name:
self.idx_name = name + '_idx'
iname = self.idx_name
idx_param = Parameter(f'index of {name}', self.enumtype,
readonly=self.readonly, influences={name})
idx_param.init({})
setattr(owner, iname, idx_param)
idx_param.__set_name__(owner, iname)
self.setProperty('influences', {iname})
if not hasattr(owner, f'write_{name}'):
# customization (like rounding up or down) might be
# achieved by adding write_<name>. if not, the default
# is rounding to the closest value
def wfunc(mobj, value, vdict=self.valuedict, fname=name, wfunc_iname=f'write_{iname}'):
getattr(mobj, wfunc_iname)(
min(vdict, key=lambda i: abs(vdict[i] - value)))
return getattr(mobj, fname)
setattr(owner, f'write_{name}', wfunc)
def __get__(self, instance, owner):
"""getter for value"""
if instance is None:
return self
return self.valuedict[instance.parameters[self.idx_name].value]
def trigger_setter(self, modobj, _):
# trigger update of float parameter on change of enum parameter
modobj.announceUpdate(self.name, getattr(modobj, self.name))
def finish(self, modobj=None):
"""register callbacks for consistency"""
super().finish(modobj)
if modobj:
modobj.addCallback(self.idx_name, self.trigger_setter, modobj)

View File

@ -61,6 +61,7 @@ class HasIO(Module):
ioname = opts.get('io') or f'{name}_io'
io = self.ioClass(ioname, srv.log.getChild(ioname), opts, srv) # pylint: disable=not-callable
io.callingModule = []
srv.modules[ioname] = io
srv.secnode.add_module(io, ioname)
self.ioDict[self.uri] = ioname
self.io = ioname

View File

@ -141,7 +141,6 @@ class SequencerMixin:
return self.Status.IDLE, ''
def stop(self):
"""stop sequence"""
if self.seq_is_alive():
self._seq_stopflag = True

View File

@ -83,14 +83,15 @@ class HasAccessibles(HasProperties):
override_values.pop(key, None)
elif key in accessibles:
override_values[key] = value
# remark: merged_properties contain already the properties of accessibles of cls
for aname, aobj in list(accessibles.items()):
if aname in override_values:
aobj = aobj.copy()
value = override_values[aname]
if value is None:
accessibles.pop(aname)
continue
aobj = aobj.create_from_value(merged_properties[aname], value)
aobj.merge(merged_properties[aname])
aobj.override(value)
# replace the bare value by the created accessible
setattr(cls, aname, aobj)
else:
@ -333,7 +334,8 @@ class Module(HasAccessibles):
self.secNode = srv.secnode
self.log = logger
self.name = name
self.paramCallbacks = {}
self.valueCallbacks = {}
self.errorCallbacks = {}
self.earlyInitDone = False
self.initModuleDone = False
self.startModuleDone = False
@ -467,7 +469,8 @@ class Module(HasAccessibles):
apply default when no value is given (in cfg or as Parameter argument)
or complain, when cfg is needed
"""
self.paramCallbacks[pname] = []
self.valueCallbacks[pname] = []
self.errorCallbacks[pname] = []
if isinstance(pobj, Limit):
basepname = pname.rpartition('_')[0]
baseparam = self.parameters.get(basepname)
@ -532,46 +535,68 @@ class Module(HasAccessibles):
err.report_error = False
return # no updates for repeated errors
err = secop_error(err)
value_err = value, err
else:
if not changed and timestamp < (pobj.timestamp or 0) + pobj.omit_unchanged_within:
# no change within short time -> omit
return
value_err = (value,)
elif not changed and timestamp < (pobj.timestamp or 0) + pobj.omit_unchanged_within:
# no change within short time -> omit
return
pobj.timestamp = timestamp or time.time()
pobj.readerror = err
for cbfunc, cbargs in self.paramCallbacks[pname]:
try:
cbfunc(*cbargs, *value_err)
except Exception:
pass
if err:
callbacks = self.errorCallbacks
pobj.readerror = arg = err
else:
callbacks = self.valueCallbacks
arg = value
pobj.readerror = None
if pobj.export:
self.updateCallback(self, pobj)
def addCallback(self, pname, callback_function, *args):
self.paramCallbacks[pname].append((callback_function, args))
cblist = callbacks[pname]
for cb in cblist:
try:
cb(arg)
except Exception:
# print(formatExtendedTraceback())
pass
def registerCallbacks(self, modobj, autoupdate=()):
"""register callbacks to another module <modobj>
whenever a self.<param> changes or changes its error state:
<modobj>.update_param(<value> [, <exc>]) is called,
where <value> is the new value and <exc> is given only in case of error.
if the method does not exist, and <param> is in autoupdate
<modobj>.announceUpdate(<pname>, <value>, <exc>) is called
with <exc> being None in case of no error.
Remark: when <modobj>.update_<param> does not accept the <exc> argument,
nothing happens (the callback is catched by try / except).
Any exceptions raised by the callback function are silently ignored.
- whenever a self.<param> changes:
<modobj>.update_<param> is called with the new value as argument.
If this method raises an exception, <modobj>.<param> gets into an error state.
If the method does not exist and <param> is in autoupdate,
<modobj>.<param> is updated to self.<param>
- whenever <self>.<param> gets into an error state:
<modobj>.error_update_<param> is called with the exception as argument.
If this method raises an error, <modobj>.<param> gets into an error state.
If this method does not exist, and <param> is in autoupdate,
<modobj>.<param> gets into the same error state as self.<param>
"""
autoupdate = set(autoupdate)
for pname in self.parameters:
cbfunc = getattr(modobj, 'update_' + pname, None)
if cbfunc:
self.addCallback(pname, cbfunc)
errfunc = getattr(modobj, 'error_update_' + pname, None)
if errfunc:
def errcb(err, p=pname, efunc=errfunc):
try:
efunc(err)
except Exception as e:
modobj.announceUpdate(p, err=e)
self.errorCallbacks[pname].append(errcb)
else:
def errcb(err, p=pname):
modobj.announceUpdate(p, err=err)
if pname in autoupdate:
self.errorCallbacks[pname].append(errcb)
updfunc = getattr(modobj, 'update_' + pname, None)
if updfunc:
def cb(value, ufunc=updfunc, efunc=errcb):
try:
ufunc(value)
except Exception as e:
efunc(e)
self.valueCallbacks[pname].append(cb)
elif pname in autoupdate:
self.addCallback(pname, modobj.announceUpdate, pname)
def cb(value, p=pname):
modobj.announceUpdate(p, value)
self.valueCallbacks[pname].append(cb)
def isBusy(self, status=None):
"""helper function for treating substates of BUSY correctly"""
@ -589,10 +614,6 @@ class Module(HasAccessibles):
# enablePoll == False: we still need the poll thread for writing values from writeDict
if hasattr(self, 'io'):
self.io.polledModules.append(self)
if not self.io.triggerPoll:
# when self.io.enablePoll is False, triggerPoll is not
# created for self.io in the else clause below
self.triggerPoll = threading.Event()
else:
self.triggerPoll = threading.Event()
self.polledModules.append(self)
@ -692,8 +713,8 @@ class Module(HasAccessibles):
for mobj in polled_modules:
pinfo = mobj.pollInfo = PollInfo(mobj.pollinterval, self.triggerPoll)
# trigger a poll interval change when self.pollinterval changes.
if 'pollinterval' in mobj.paramCallbacks:
mobj.addCallback('pollinterval', pinfo.update_interval)
if 'pollinterval' in mobj.valueCallbacks:
mobj.valueCallbacks['pollinterval'].append(pinfo.update_interval)
for pname, pobj in mobj.parameters.items():
rfunc = getattr(mobj, 'read_' + pname)

View File

@ -36,7 +36,6 @@ from .modulebase import Module
class Readable(Module):
"""basic readable module"""
# pylint: disable=invalid-name
Status = Enum('Status',
IDLE=StatusType.IDLE,
WARN=StatusType.WARN,
@ -93,7 +92,7 @@ class Drivable(Writable):
@Command(None, result=None)
def stop(self):
"""not implemented - this is a no-op"""
"""cease driving, go to IDLE state"""
class Communicator(HasComlog, Module):

View File

@ -57,17 +57,13 @@ class Accessible(HasProperties):
def as_dict(self):
return self.propertyValues
def create_from_value(self, properties, value):
"""return a clone with given value and inherited properties"""
raise NotImplementedError
def clone(self, properties, **kwds):
"""return a clone of ourselfs with inherited properties"""
def override(self, value):
"""override with a bare value"""
raise NotImplementedError
def copy(self):
"""return a (deep) copy of ourselfs"""
return self.clone(self.propertyValues)
raise NotImplementedError
def updateProperties(self, merged_properties):
"""update merged_properties with our own properties"""
@ -238,15 +234,13 @@ class Parameter(Accessible):
# avoid export=True overrides export=<name>
self.ownProperties['export'] = self.export
def clone(self, properties, **kwds):
"""return a clone of ourselfs with inherited properties"""
res = type(self)(**kwds)
def copy(self):
"""return a (deep) copy of ourselfs"""
res = type(self)()
res.name = self.name
res.init(properties)
res.init(res.ownProperties)
res.init(self.propertyValues)
if 'datatype' in self.propertyValues:
res.datatype = res.datatype.copy()
res.finish()
return res
def updateProperties(self, merged_properties):
@ -259,9 +253,9 @@ class Parameter(Accessible):
merged_properties.pop(key)
merged_properties.update(self.ownProperties)
def create_from_value(self, properties, value):
"""return a clone with given value and inherited properties"""
return self.clone(properties, value=self.datatype(value))
def override(self, value):
"""override default"""
self.value = self.datatype(value)
def merge(self, merged_properties):
"""merge with inherited properties
@ -396,7 +390,7 @@ class Command(Accessible):
else:
# goodie: allow @Command instead of @Command()
self.func = argument # this is the wrapped method!
if argument.__doc__ is not None:
if argument.__doc__:
self.description = inspect.cleandoc(argument.__doc__)
self.name = self.func.__name__ # this is probably not needed
self._inherit = inherit # save for __set_name__
@ -445,37 +439,38 @@ class Command(Accessible):
f' members!: {params} != {members}')
self.argument.optional = [p for p,v in sig.parameters.items()
if v.default is not inspect.Parameter.empty]
if 'description' not in self.ownProperties and func.__doc__ is not None:
if 'description' not in self.propertyValues and func.__doc__:
self.description = inspect.cleandoc(func.__doc__)
self.ownProperties['description'] = self.description
self.func = func
return self
def clone(self, properties, **kwds):
"""return a clone of ourselfs with inherited properties"""
res = type(self)(**kwds)
def copy(self):
"""return a (deep) copy of ourselfs"""
res = type(self)()
res.name = self.name
res.func = self.func
res.init(properties)
res.init(res.ownProperties)
res.init(self.propertyValues)
if res.argument:
res.argument = res.argument.copy()
if res.result:
res.result = res.result.copy()
res.finish()
self.finish()
return res
def updateProperties(self, merged_properties):
"""update merged_properties with our own properties"""
merged_properties.update(self.ownProperties)
def create_from_value(self, properties, value):
"""return a clone with given value and inherited properties
def override(self, value):
"""override method
this is needed when the @Command is missing on a method overriding a command"""
if not callable(value):
raise ProgrammingError(f'{self.name} = {value!r} is overriding a Command')
return self.clone(properties)(value)
self.func = value
if value.__doc__:
self.description = inspect.cleandoc(value.__doc__)
def merge(self, merged_properties):
"""merge with inherited properties

View File

@ -84,7 +84,9 @@ class PersistentMixin(Module):
flag = getattr(pobj, 'persistent', False)
if flag:
if flag == 'auto':
self.addCallback(pname, self.saveParameters)
def cb(value, m=self):
m.saveParameters()
self.valueCallbacks[pname].append(cb)
self.initData[pname] = pobj.value
if not pobj.given:
if pname in loaded:
@ -127,18 +129,16 @@ class PersistentMixin(Module):
self.writeInitParams()
return loaded
def saveParameters(self, _=None):
def saveParameters(self):
"""save persistent parameters
- to be called regularly explicitly by the module
- the caller has to make sure that this is not called after
a power down of the connected hardware before loadParameters
dummy argument to avoid closure for callback
"""
if self.writeDict:
# do not save before all values are written to the hw, as potentially
# factory default values were read in the meantime
# factory default values were read in the mean time
return
self.__save_params()

View File

@ -71,7 +71,7 @@ class ProxyModule(HasIO, Module):
pname, pobj = params.popitem()
props = remoteparams.get(pname, None)
if props is None:
if pobj.export and pname != 'status':
if pobj.export:
self.log.warning('remote parameter %s:%s does not exist', self.module, pname)
continue
dt = props['datatype']
@ -108,19 +108,17 @@ class ProxyModule(HasIO, Module):
# for now, the error message must be enough
def nodeStateChange(self, online, state):
disconnected = Readable.Status.ERROR, 'disconnected'
if online:
if not self._consistency_check_done:
self._check_descriptive_data()
self._consistency_check_done = True
if self.status == disconnected:
self.status = Readable.Status.IDLE, 'connected'
else:
newstatus = Readable.Status.ERROR, 'disconnected'
readerror = CommunicationFailedError('disconnected')
if self.status != disconnected:
if self.status != newstatus:
for pname in set(self.parameters) - set(('module', 'status')):
self.announceUpdate(pname, None, readerror)
self.status = disconnected
self.announceUpdate('status', newstatus)
def checkProperties(self):
pass # skip
@ -195,7 +193,7 @@ def proxy_class(remote_class, name=None):
attrs[aname] = pobj
def rfunc(self, pname=aname):
value, _, readerror = self._secnode.getParameter(self.module, pname, True)
value, _, readerror = self._secnode.getParameter(self.name, pname, True)
if readerror:
raise readerror
return value
@ -205,7 +203,7 @@ def proxy_class(remote_class, name=None):
if not pobj.readonly:
def wfunc(self, value, pname=aname):
value, _, readerror = self._secnode.setParameter(self.module, pname, value)
value, _, readerror = self._secnode.setParameter(self.name, pname, value)
if readerror:
raise readerror
return value
@ -216,7 +214,7 @@ def proxy_class(remote_class, name=None):
cobj = aobj.copy()
def cfunc(self, arg=None, cname=aname):
return self._secnode.execCommand(self.module, cname, arg)[0]
return self._secnode.execCommand(self.name, cname, arg)[0]
attrs[aname] = cobj(cfunc)

View File

@ -239,7 +239,6 @@ class HasStates:
@Command
def stop(self):
"""stop state machine"""
self.stop_machine()
def final_status(self, code=IDLE, text=''):

164
frappy/structparam.py Normal file
View File

@ -0,0 +1,164 @@
# *****************************************************************************
#
# 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>
#
# *****************************************************************************
"""convenience class to create a struct Parameter together with indivdual params
Usage:
class Controller(Drivable):
...
ctrlpars = StructParam('ctrlpars struct', [
('pid_p', 'p', Parameter('control parameter p', FloatRange())),
('pid_i', 'i', Parameter('control parameter i', FloatRange())),
('pid_d', 'd', Parameter('control parameter d', FloatRange())),
], readonly=False)
...
then implement either read_ctrlpars and write_ctrlpars or
read_pid_p, read_pid_i, read_pid_d, write_pid_p, write_pid_i and write_pid_d
the methods not implemented will be created automatically
"""
from frappy.core import Parameter, Property
from frappy.datatypes import BoolType, DataType, StructOf, ValueType
from frappy.errors import ProgrammingError
class StructParam(Parameter):
"""create a struct parameter together with individual parameters
in addition to normal Parameter arguments:
:param paramdict: dict <member name> of Parameter(...)
:param prefix_or_map: either a prefix for the parameter name to add to the member name
or a dict <member name> or <paramerter name>
"""
# use properties, as simple attributes are not considered on copy()
paramdict = Property('dict <parametername> of Parameter(...)', ValueType())
hasStructRW = Property('has a read_<struct param> or write_<struct param> method',
BoolType(), default=False)
insideRW = 0 # counter for avoiding multiple superfluous updates
def __init__(self, description=None, paramdict=None, prefix_or_map='', *, datatype=None, readonly=False, **kwds):
if isinstance(paramdict, DataType):
raise ProgrammingError('second argument must be a dict of Param')
if datatype is None and paramdict is not None: # omit the following on Parameter.copy()
if isinstance(prefix_or_map, str):
prefix_or_map = {m: prefix_or_map + m for m in paramdict}
for membername, param in paramdict.items():
param.name = prefix_or_map[membername]
datatype = StructOf(**{m: p.datatype for m, p in paramdict.items()})
kwds['influences'] = [p.name for p in paramdict.values()]
self.updateEnable = {}
super().__init__(description, datatype, paramdict=paramdict, readonly=readonly, **kwds)
def __set_name__(self, owner, name):
# names of access methods of structed param (e.g. ctrlpars)
struct_read_name = f'read_{name}' # e.g. 'read_ctrlpars'
struct_write_name = f'write_{name}' # e.h. 'write_ctrlpars'
self.hasStructRW = hasattr(owner, struct_read_name) or hasattr(owner, struct_write_name)
for membername, param in self.paramdict.items():
pname = param.name
changes = {
'readonly': self.readonly,
'influences': set(param.influences) | {name},
}
param.ownProperties.update(changes)
param.init(changes)
setattr(owner, pname, param)
param.__set_name__(owner, param.name)
if self.hasStructRW:
rname = f'read_{pname}'
if not hasattr(owner, rname):
def rfunc(self, membername=membername, struct_read_name=struct_read_name):
return getattr(self, struct_read_name)()[membername]
rfunc.poll = False # read_<struct param> is polled only
setattr(owner, rname, rfunc)
if not self.readonly:
wname = f'write_{pname}'
if not hasattr(owner, wname):
def wfunc(self, value, membername=membername,
name=name, rname=rname, struct_write_name=struct_write_name):
valuedict = dict(getattr(self, name))
valuedict[membername] = value
getattr(self, struct_write_name)(valuedict)
return getattr(self, rname)()
setattr(owner, wname, wfunc)
if not self.hasStructRW:
if not hasattr(owner, struct_read_name):
def struct_read_func(self, name=name, flist=tuple(
(m, f'read_{p.name}') for m, p in self.paramdict.items())):
pobj = self.parameters[name]
# disable updates generated from the callbacks of individual params
pobj.insideRW += 1 # guarded by self.accessLock
try:
return {m: getattr(self, f)() for m, f in flist}
finally:
pobj.insideRW -= 1
setattr(owner, struct_read_name, struct_read_func)
if not (self.readonly or hasattr(owner, struct_write_name)):
def struct_write_func(self, value, name=name, funclist=tuple(
(m, f'write_{p.name}') for m, p in self.paramdict.items())):
pobj = self.parameters[name]
pobj.insideRW += 1 # guarded by self.accessLock
try:
return {m: getattr(self, f)(value[m]) for m, f in funclist}
finally:
pobj.insideRW -= 1
setattr(owner, struct_write_name, struct_write_func)
super().__set_name__(owner, name)
def finish(self, modobj=None):
"""register callbacks for consistency"""
super().finish(modobj)
if modobj:
if self.hasStructRW:
def cb(value, modobj=modobj, structparam=self):
for membername, param in structparam.paramdict.items():
setattr(modobj, param.name, value[membername])
modobj.valueCallbacks[self.name].append(cb)
else:
for membername, param in self.paramdict.items():
def cb(value, modobj=modobj, structparam=self, membername=membername):
if not structparam.insideRW:
prev = dict(getattr(modobj, structparam.name))
prev[membername] = value
setattr(modobj, structparam.name, prev)
modobj.valueCallbacks[param.name].append(cb)

View File

@ -120,7 +120,6 @@ class MagneticField(Drivable):
)
heatswitch = Attached(Switch, description='name of heat switch device')
# pylint: disable=invalid-name
Status = Enum(Drivable.Status, PERSIST=PERSIST, PREPARE=301, RAMPING=302, FINISH=303)
status = Parameter(datatype=TupleOf(EnumType(Status), StringType()))
@ -194,7 +193,6 @@ class MagneticField(Drivable):
self.log.error(self, 'main thread exited unexpectedly!')
def stop(self):
"""stop at current value"""
self.write_target(self.read_value())

View File

@ -36,12 +36,12 @@ from time import sleep, time as currenttime
import PyTango
from frappy.datatypes import ArrayOf, EnumType, FloatRange, IntRange, \
LimitsType, StatusType, StringType, TupleOf, ValueType
LimitsType, StringType, TupleOf, ValueType
from frappy.errors import CommunicationFailedError, ConfigError, \
HardwareError, ProgrammingError, WrongTypeError
from frappy.lib import lazy_property
from frappy.modules import Command, Drivable, Module, Parameter, Property, \
Readable, Writable
from frappy.modules import Command, Drivable, Module, Parameter, Readable, \
StatusType, Writable, Property
#####
@ -641,7 +641,6 @@ class AnalogOutput(PyTangoDevice, Drivable):
sleep(0.3)
def stop(self):
"""cease driving, go to IDLE state"""
self._dev.Stop()

View File

@ -194,19 +194,19 @@ class Nmr(Readable):
x = val['xval'][:len(val['yval'])]
return (x, val['yval'])
@Command(IntRange(1), result=TupleOf(ArrayOf(string, maxlen=100),
@Command(result=TupleOf(ArrayOf(string, maxlen=100),
ArrayOf(floating, maxlen=100)))
def get_amplitude(self, count):
"""Last <count> amplitude datapoints."""
rv = self.cell.cell.nmr_paramlog_get('amplitude', count)
def get_amplitude(self):
"""Last 20 amplitude datapoints."""
rv = self.cell.cell.nmr_paramlog_get('amplitude', 20)
x = [ str(timestamp) for timestamp in rv['xval']]
return (x,rv['yval'])
@Command(IntRange(1), result=TupleOf(ArrayOf(string, maxlen=100),
@Command(result=TupleOf(ArrayOf(string, maxlen=100),
ArrayOf(floating, maxlen=100)))
def get_phase(self, count):
"""Last <count> phase datapoints."""
val = self.cell.cell.nmr_paramlog_get('phase', count)
def get_phase(self):
"""Last 20 phase datapoints."""
val = self.cell.cell.nmr_paramlog_get('phase', 20)
return ([str(timestamp) for timestamp in val['xval']], val['yval'])

229
frappy_psi/HP.py Normal file
View File

@ -0,0 +1,229 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# *****************************************************************************
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation; either version 2 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc.,
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# Module authors: Oksana Shliakhtun <oksana.shliakhtun@psi.ch>
# *****************************************************************************
"""Hewlett-Packard HP34401A Multimeter (not finished)"""
import re
from frappy.core import HasIO, Readable, Parameter, FloatRange, EnumType, StatusType, IDLE, ERROR, WARN
def string_to_value(value):
"""
Converting the value to float, removing the units, converting the prefix into the number.
:param value: value
:return: float value without units
"""
value_with_unit = re.compile(r'(\d+)([pnumkMG]?)')
value, pfx = value_with_unit.match(value).groups()
pfx_dict = {'p': 1e-12, 'n': 1e-9, 'u': 1e-6, 'm': 1e-3, 'k': 1e3, 'M': 1e6, 'G': 1e9}
if pfx in pfx_dict:
value = round(float(value) * pfx_dict[pfx], 12)
return float(value)
class HP_IO(HasIO):
end_of_line = b'\n'
identification = [('*IDN?', r'HEWLETT-PACKARD,34401A,0,.*')]
class HP34401A(HP_IO):
status = Parameter(datatype=StatusType(Readable, 'BUSY'))
autorange = Parameter('autorange_on', EnumType('autorange', off=0, on=1), readonly=False, default=0)
def comm(self, cmd): # read until \n
string = f'{cmd}\n'
n_string = string.encode()
response = self.communicate(n_string)
if response:
return response
response = self.communicate(n_string)
return response if response else None
def read_range(self, function):
return self.comm(f'{function}:range?')
def write_range(self, function, range):
return self.comm(f'{function}:range {range}')
def write_autorange(self, function):
cmd = f'{function}:range:auto {"on" if self.autorange == 0 else "off"}'
self.comm(cmd)
return self.comm(f'{function}:range:auto?')
def read_resolution(self, function):
return self.comm(f'{function}:resolution?')
def write_resolution(self, function, resolution):
self.comm(f'{function}:resolution {resolution}')
return self.comm(f'{function}:resolution?')
def read_status(self):
stb = int(self.comm('*STB?'))
esr = int(self.comm('*ESR?'))
if esr & (1 << 3):
return ERROR, 'self-test/calibration/reading failed'
if esr & (1 << 4):
return ERROR, 'execution error'
if esr & (1 << 5):
return ERROR, 'syntax error'
if esr & (1 << 2):
return ERROR, 'query error'
if stb & (1 << 3):
return WARN, 'questionable data'
if stb & (1 << 5):
return WARN, 'standard event register is not empty'
if stb & (1 << 6):
return WARN, 'requested service'
if any(stb & (1 << i) for i in range(3) or stb & (1 << 7)):
return IDLE, ''
if esr & (1 << 6):
return IDLE, ''
if esr & (1 << 7):
return IDLE, ''
if stb & (1 << 4):
return IDLE, 'message available'
if esr & (1 << 0):
return IDLE, 'operation complete'
if esr & (1 << 1):
return IDLE, 'not used'
class Voltage(HP34401A, Readable):
value = Parameter('voltage', datatype=FloatRange(0.1, 1000), unit='V')
range = Parameter('voltage sensitivity value', FloatRange(), unit='V', default=1, readonly=False)
resolution = Parameter('resolution')
mode = Parameter('measurement mode: ac/dc', readonly=False)
ioClass = HP_IO
MODE_NAMES = {0: 'dc', 1: 'ac'}
VOLT_RANGE = ['100mV', '1V', '10V', '100V', '1000V']
v_range = Parameter('voltage range', EnumType('voltage index range',
{name: idx for idx, name in enumerate(VOLT_RANGE)}), readonly=False)
acdc = None
def write_mode(self, mode):
"""
Set the mode - AC or DC
:param mode: AC/DC
:return:
"""
if mode == 1:
self.comm(f'configure:voltage:AC {self.range}, {self.resolution}')
else:
self.comm(f'configure:voltage:DC {self.range}, {self.resolution}')
self.acdc = self.MODE_NAMES[mode]
return self.comm(f'function?')
def read_value(self):
"""
Makes a AC/DC voltage measurement.
:return: AC/DC value
"""
return self.comm(f'measure:voltage:{self.acdc}?')
def write_autorange_acdc(self, function):
full_function = f'{function}:{self.acdc}'
return self.write_autorange(full_function)
def read_range_voltage(self):
return self.read_range(f'voltage:{self.acdc}')
def write_range_voltage(self, range):
return self.write_range(f'voltage:{self.acdc}', range)
def write_autorange_voltage(self):
return self.write_autorange_acdc('voltage')
def read_resolution_voltage(self):
return self.read_resolution(f'voltage:{self.acdc}')
def write_resolution_voltage(self, resolution):
return self.write_resolution(f'voltage:{self.acdc}', resolution)
class Current(HP34401A, Readable, Voltage):
value = Parameter('current', FloatRange, unit='A')
range = Parameter('current range', FloatRange)
CURR_RANGE_AC = ['10mA', '100mA', '1A', '3A']
CURR_RANGE_DC = ['1A', '3A']
def read_range_current(self):
return self.read_range(f'current:{self.acdc}')
def write_autorange_current(self):
return self.write_autorange_acdc('current')
def write_range_current(self, range):
return self.write_range(f'current:{self.acdc}', range)
def read_resolution_current(self):
return self.read_resolution(f'current:{self.acdc}')
def write_resolution_current(self, resolution):
return self.write_resolution(f'current:{self.acdc}', resolution)
class Resistance(HP34401A, Readable):
value = Parameter('resistance')
mode = Parameter('measurement mode: 2-/4-wire ohms', EnumType(two_wire=2, four_wire=4), readonly=False)
resolution = Parameter('resistance measurement resolution')
range = Parameter('resistance measurement range')
RESIST_RANGE = ['100Om', '1kOm', '10kOm', '100kOm', '1MOm', '10MOm', '100MOm']
FUNCTION_MAP = {2: 'resistance', 4: 'fresistance'}
def write_range_resistance(self, range):
return self.write_range(f'{self.FUNCTION_MAP[self.mode]}', range)
def read_range_resistance(self):
return self.read_range(f'{self.FUNCTION_MAP[self.mode]}')
def write_mode(self, mode):
if mode == 2:
self.comm(f'configure:resistance {self.range},{self.resolution}')
elif mode == 4:
self.comm(f'configure:fresistance {self.range}, {self.resolution}')
return self.comm('configure?')
def write_autorange_resistance(self):
return self.write_autorange(self.FUNCTION_MAP[self.mode])
def read_resolution_resistance(self):
return self.read_resolution(f'{self.FUNCTION_MAP[self.mode]}')
def write_resolution_resistance(self, resolution):
return self.write_resolution(f'{self.FUNCTION_MAP[self.mode]}', resolution)
class Frequency(HP34401A, Readable):
value = Parameter('frequency', FloatRange(3, 300e3), unit='Hz')
def write_autorange_frequency(self):
return self.write_autorange('frequency')
def read_resolution_frequency(self):
return self.read_resolution('frequency')
def write_resolution_frequency(self, resolution):
return self.write_resolution('frequency', resolution)

View File

@ -15,6 +15,7 @@
#
# Module authors: Oksana Shliakhtun <oksana.shliakhtun@psi.ch>
# *****************************************************************************
"""Stanford Research Systems SR830 DS Lock-in Amplifier"""
import re
import time
@ -25,6 +26,11 @@ from frappy.errors import IsBusyError
def string_to_value(value):
"""
Converting the value to float, removing the units, converting the prefix into the number.
:param value: value
:return: float value without units
"""
value_with_unit = re.compile(r'(\d+)([pnumkMG]?)')
value, pfx = value_with_unit.match(value).groups()
pfx_dict = {'p': 1e-12, 'n': 1e-9, 'u': 1e-6, 'm': 1e-3, 'k': 1e3, 'M': 1e6, 'G': 1e9}
@ -40,6 +46,13 @@ class SR830_IO(StringIO):
class StanfRes(HasIO, Readable):
def set_par(self, cmd, *args):
"""
Set parameter.
Query commands are the same as setting commands, but they have a question mark.
:param cmd: command
:param args: value(s)
:return: reply
"""
head = ','.join([cmd] + [a if isinstance(a, str) else f'{a:g}' for a in args])
tail = cmd.replace(' ', '? ')
new_tail = re.sub(r'[0-9.]+', '', tail)
@ -149,6 +162,10 @@ class XY(StanfRes):
return IDLE, ''
def read_value(self):
"""
Read XY. The manual autorange implemented.
:return:
"""
if self.read_status()[0] == BUSY:
raise IsBusyError('changing gain')
reply = self.get_par('SNAP? 1, 2')
@ -166,11 +183,13 @@ class XY(StanfRes):
return int(self.get_par('SENS?'))
def read_range(self):
"""Sensitivity range value"""
idx = self.read_irange()
name = self.SEN_RANGE[idx]
return string_to_value(name)
def write_irange(self, irange):
"""Index of sensitivity from the range"""
value = int(irange)
self.set_par(f'SENS {value}')
self._autogain_started = time.time()
@ -178,6 +197,12 @@ class XY(StanfRes):
return value
def write_range(self, target):
"""
Setting the sensitivity range.
cl_idx/cl_value is the closest index/value from the range to the target
:param target:
:return: closest value of the sensitivity range
"""
target = float(target)
cl_idx = None
cl_value = float('inf')
@ -194,6 +219,7 @@ class XY(StanfRes):
return cl_value
def read_itc(self):
"""Time constant index from the range"""
return int(self.get_par(f'OFLT?'))
def write_itc(self, itc):
@ -201,11 +227,18 @@ class XY(StanfRes):
return self.set_par(f'OFLT {value}')
def read_tc(self):
"""Read time constant value from the range"""
idx = self.read_itc()
name = self.TIME_CONST[idx]
return string_to_value(name)
def write_tc(self, target):
"""
Setting the time constant from the range.
cl_idx/cl_value is the closest index/value from the range to the target
:param target: time constant
:return: closest time constant value
"""
target = float(target)
cl_idx = None
cl_value = float('inf')

View File

@ -17,690 +17,248 @@
# Markus Zolliker <markus.zolliker@psi.ch>
# *****************************************************************************
import sys
import time
import threading
from frappy.core import Drivable, Parameter, Command, Property, Module, HasIO, \
ERROR, WARN, BUSY, IDLE, nopoll, Limit
from frappy.core import Drivable, Parameter, Command, Property, ERROR, WARN, BUSY, IDLE, Done, nopoll
from frappy.features import HasTargetLimits, HasSimpleOffset
from frappy.datatypes import IntRange, FloatRange, StringType, BoolType
from frappy.errors import BadValueError, HardwareError, ConfigError
from frappy.errors import ConfigError, BadValueError
sys.path.append('/home/l_samenv/Documents/anc350/Linux64/userlib/lib')
from PyANC350v4 import Positioner
class IO(Module):
"""'communication' module for attocube controller
DIRECTION_NAME = {1: 'forward', -1: 'backward'}
why an extra class:
- HasIO assures that a single common communicator is used
- access must be thread safe
class FreezeStatus:
"""freeze status for some time
hardware quite often does not treat status correctly: on a target change it
may take some time to return the 'busy' status correctly.
in classes with this mixin, within :meth:`write_target` call
self.freeze_status(0.5, BUSY, 'changed target')
a wrapper around read_status will take care that the status will be the given value,
for at least the given delay. This does NOT cover the case when self.status is set
directly from an other method.
"""
uri = Property('dummy uri, only one controller may exists',
StringType())
export = False
_hw = None
_lock = None
used_axes = set()
__freeze_status_until = 0
def initModule(self):
if self._hw is None:
IO._lock = threading.Lock()
IO._hw = Positioner()
super().initModule()
def __init_subclass__(cls):
def wrapped(self, inner=cls.read_status):
if time.time() < self.__freeze_status_until:
return Done
return inner(self)
def shutdownModule(self):
if IO._hw:
IO._hw.disconnect()
IO._hw = None
cls.read_status = wrapped
super().__init_subclass__()
def configureAQuadBIn(self, axisNo, enable, resolution):
"""Enables and configures the A-Quad-B (quadrature) input for the target position.
Parameters
axisNo Axis number (0 ... 2)
enable Enable (1) or disable (0) A-Quad-B input
resolution A-Quad-B step width in m. Internal resolution is 1 nm.
Returns
None
"""
with self._lock:
return self._hw.configureAQuadBIn(axisNo, enable, resolution)
def configureAQuadBOut(self, axisNo, enable, resolution, clock):
"""Enables and configures the A-Quad-B output of the current position.
Parameters
axisNo Axis number (0 ... 2)
enable Enable (1) or disable (0) A-Quad-B output
resolution A-Quad-B step width in m; internal resolution is 1 nm
clock Clock of the A-Quad-B output [s]. Allowed range is 40ns ... 1.3ms; internal resulution is 20ns.
Returns
None
"""
with self._lock:
return self._hw.configureAQuadBOut(axisNo, enable, resolution, clock)
def configureExtTrigger(self, axisNo, mode):
"""Enables the input trigger for steps.
Parameters
axisNo Axis number (0 ... 2)
mode Disable (0), Quadratur (1), Trigger(2) for external triggering
Returns
None
"""
with self._lock:
return self._hw.configureExtTrigger(axisNo, mode)
def configureNslTriggerAxis(self, axisNo):
"""Selects Axis for NSL Trigger.
Parameters
axisNo Axis number (0 ... 2)
Returns
None
"""
with self._lock:
return self._hw.configureNslTriggerAxis(axisNo)
def configureRngTrigger(self, axisNo, lower, upper):
"""Configure lower position for range Trigger.
Parameters
axisNo Axis number (0 ... 2)
lower Lower position for range trigger (nm)
upper Upper position for range trigger (nm)
Returns
None
"""
with self._lock:
return self._hw.configureRngTrigger(axisNo, lower, upper)
def configureRngTriggerEps(self, axisNo, epsilon):
"""Configure hysteresis for range Trigger.
Parameters
axisNo Axis number (0 ... 2)
epsilon hysteresis in nm / mdeg
Returns
None
"""
with self._lock:
return self._hw.configureRngTriggerEps(axisNo, epsilon)
def configureRngTriggerPol(self, axisNo, polarity):
"""Configure lower position for range Trigger.
Parameters
axisNo Axis number (0 ... 2)
polarity Polarity of trigger signal when position is between lower and upper Low(0) and High(1)
Returns
None
"""
with self._lock:
return self._hw.configureRngTriggerPol(axisNo, polarity)
def getActuatorName(self, axisNo):
"""Get the name of the currently selected actuator
Parameters
axisNo Axis number (0 ... 2)
Returns
name Name of the actuator
"""
with self._lock:
return self._hw.getActuatorName(axisNo)
def getActuatorType(self, axisNo):
"""Get the type of the currently selected actuator
Parameters
axisNo Axis number (0 ... 2)
Returns
type_ Type of the actuator {0: linear, 1: goniometer, 2: rotator}
"""
with self._lock:
return self._hw.getActuatorType(axisNo)
def getAmplitude(self, axisNo):
"""Reads back the amplitude parameter of an axis.
Parameters
axisNo Axis number (0 ... 2)
Returns
amplitude Amplitude V
"""
with self._lock:
return self._hw.getAmplitude(axisNo)
def getAxisStatus(self, axisNo):
"""Reads status information about an axis of the device.
Parameters
axisNo Axis number (0 ... 2)
Returns
connected Output: If the axis is connected to a sensor.
enabled Output: If the axis voltage output is enabled.
moving Output: If the axis is moving.
target Output: If the target is reached in automatic positioning
eotFwd Output: If end of travel detected in forward direction.
eotBwd Output: If end of travel detected in backward direction.
error Output: If the axis' sensor is in error state.
"""
with self._lock:
return self._hw.getAxisStatus(axisNo)
def getFrequency(self, axisNo):
"""Reads back the frequency parameter of an axis.
Parameters
axisNo Axis number (0 ... 2)
Returns
frequency Output: Frequency in Hz
"""
with self._lock:
return self._hw.getFrequency(axisNo)
def getPosition(self, axisNo):
"""Retrieves the current actuator position. For linear type actuators the position unit is m; for goniometers and rotators it is degree.
Parameters
axisNo Axis number (0 ... 2)
Returns
position Output: Current position [m] or [°]
"""
with self._lock:
return self._hw.getPosition(axisNo)
def measureCapacitance(self, axisNo):
"""Performs a measurement of the capacitance of the piezo motor and returns the result. If no motor is connected, the result will be 0. The function doesn't return before the measurement is complete; this will take a few seconds of time.
Parameters
axisNo Axis number (0 ... 2)
Returns
cap Output: Capacitance [F]
"""
with self._lock:
return self._hw.measureCapacitance(axisNo)
def selectActuator(self, axisNo, actuator):
"""Selects the actuator to be used for the axis from actuator presets.
Parameters
axisNo Axis number (0 ... 2)
actuator Actuator selection (0 ... 255)
0: ANPg101res
1: ANGt101res
2: ANPx51res
3: ANPx101res
4: ANPx121res
5: ANPx122res
6: ANPz51res
7: ANPz101res
8: ANR50res
9: ANR51res
10: ANR101res
11: Test
Returns
None
"""
with self._lock:
return self._hw.selectActuator(axisNo, actuator)
def setAmplitude(self, axisNo, amplitude):
"""Sets the amplitude parameter for an axis
Parameters
axisNo Axis number (0 ... 2)
amplitude Amplitude in V, internal resolution is 1 mV
Returns
None
"""
with self._lock:
return self._hw.setAmplitude(axisNo, amplitude)
def setAxisOutput(self, axisNo, enable, autoDisable):
"""Enables or disables the voltage output of an axis.
Parameters
axisNo Axis number (0 ... 2)
enable Enables (1) or disables (0) the voltage output.
autoDisable If the voltage output is to be deactivated automatically when end of travel is detected.
Returns
None
"""
with self._lock:
return self._hw.setAxisOutput(axisNo, enable, autoDisable)
def setDcVoltage(self, axisNo, voltage):
"""Sets the DC level on the voltage output when no sawtooth based motion is active.
Parameters
axisNo Axis number (0 ... 2)
voltage DC output voltage [V], internal resolution is 1 mV
Returns
None
"""
with self._lock:
return self._hw.setDcVoltage(axisNo, voltage)
def setFrequency(self, axisNo, frequency):
"""Sets the frequency parameter for an axis
Parameters
axisNo Axis number (0 ... 2)
frequency Frequency in Hz, internal resolution is 1 Hz
Returns
None
"""
with self._lock:
return self._hw.setFrequency(axisNo, frequency)
def setTargetPosition(self, axisNo, target):
"""Sets the target position for automatic motion, see ANC_startAutoMove. For linear type actuators the position unit is m, for goniometers and rotators it is degree.
Parameters
axisNo Axis number (0 ... 2)
target Target position [m] or [°]. Internal resulution is 1 nm or 1 µ°.
Returns
None
"""
with self._lock:
return self._hw.setTargetPosition(axisNo, target)
def setTargetRange(self, axisNo, targetRg):
"""Defines the range around the target position where the target is considered to be reached.
Parameters
axisNo Axis number (0 ... 2)
targetRg Target range [m] or [°]. Internal resulution is 1 nm or 1 µ°.
Returns
None
"""
with self._lock:
return self._hw.setTargetRange(axisNo, targetRg)
def startAutoMove(self, axisNo, enable, relative):
"""Switches automatic moving (i.e. following the target position) on or off
Parameters
axisNo Axis number (0 ... 2)
enable Enables (1) or disables (0) automatic motion
relative If the target position is to be interpreted absolute (0) or relative to the current position (1)
Returns
None
"""
with self._lock:
return self._hw.startAutoMove(axisNo, enable, relative)
def startContinuousMove(self, axisNo, start, backward):
"""Starts or stops continous motion in forward direction. Other kinds of motions are stopped.
Parameters
axisNo Axis number (0 ... 2)
start Starts (1) or stops (0) the motion
backward If the move direction is forward (0) or backward (1)
Returns
None
"""
with self._lock:
return self._hw.startContinuousMove(axisNo, start, backward)
def startSingleStep(self, axisNo, backward):
"""Triggers a single step in desired direction.
Parameters
axisNo Axis number (0 ... 2)
backward If the step direction is forward (0) or backward (1)
Returns
None
"""
with self._lock:
return self._hw.startSingleStep(axisNo, backward)
def freeze_status(self, delay, code=BUSY, text='changed target'):
"""freezze status to the given value for the given delay"""
self.__freeze_status_until = time.time() + delay
self.status = code, text
class Stopped(RuntimeError):
"""thread was stopped"""
class Axis(HasIO, Drivable):
class Axis(HasTargetLimits, FreezeStatus, Drivable):
axis = Property('axis number', IntRange(0, 2), 0)
value = Parameter('axis position', FloatRange(unit='deg'))
frequency = Parameter('frequency', FloatRange(1, unit='Hz'), readonly=False)
amplitude = Parameter('amplitude', FloatRange(0, unit='V'), readonly=False)
gear = Parameter('gear factor', FloatRange(), readonly=False, value=1)
tolerance = Parameter('positioning tolerance', FloatRange(0, unit='$'),
readonly=False, default=0.01)
sensor_connected = Parameter('a sensor is connected', BoolType())
gear = Parameter('gear factor', FloatRange(), readonly=False, default=1, initwrite=True)
tolerance = Parameter('positioning tolerance', FloatRange(0, unit='$'), readonly=False, default=0.01)
output = Parameter('enable output', BoolType(), readonly=False)
info = Parameter('axis info', StringType())
statusbits = Parameter('status bits', StringType())
step_mode = Parameter('step mode (soft closed loop)', BoolType(),
default=False, readonly=False, group='step_mode')
timeout = Parameter('timeout after no progress detected', FloatRange(0),
default=1, readonly=False, group='step_mode')
steps_fwd = Parameter('forward steps / main unit', FloatRange(0), unit='$/s',
default=0, readonly=False, group='step_mode')
steps_bwd = Parameter('backward steps / main unit', FloatRange(0, unit='$/s'),
default=0, readonly=False, group='step_mode')
delay = Parameter('delay between tries within loop', FloatRange(0, unit='s'),
readonly=False, default=0.05, group='step_mode')
maxstep = Parameter('max. step duration', FloatRange(0, unit='s'),
default=0.25, readonly=False, group='step_mode')
prop = Parameter('factor for control loop', FloatRange(0, 1),
readonly=False, default=0.8, group='step_mode')
uri = 'ANC'
ioClass = IO
target_min = Limit()
target_max = Limit()
fast_interval = 0.25
_hw = None
_hw = Positioner()
_scale = 1 # scale for custom units
_move_steps = 0 # number of steps to move (used by move command)
SCALES = {'deg': 1, 'm': 1, 'mm': 1000, 'um': 1000000, 'µm': 1000000}
_thread = None
_moving_since = 0
_status = IDLE, ''
_calib_range = None
_try_cnt = 0
_at_target = False
_direction = 1 # move direction
_idle_status = IDLE, ''
_error_state = '' # empty string: no error
_history = None
_check_sensor = False
_try_count = 0
def initModule(self):
super().initModule()
self._stopped = threading.Event()
if self.axis in IO.used_axes:
raise ConfigError(f'a module with axisNo={self.axis} already exists')
IO.used_axes.add(self.axis)
def initialReads(self):
self.read_info()
super().initialReads()
def shutdownModule(self):
IO.used_axes.discard(self.axis)
def read_value(self):
if self._thread:
return self.value
def __init__(self, name, logger, opts, srv):
unit = opts.pop('unit', 'deg')
opts['value.unit'] = unit
try:
return self._read_pos()
except Stopped:
return self.value
self._scale = self.SCALES[unit] * opts.get('gear', 1)
except KeyError as e:
raise ConfigError('unsupported unit: %s' % unit)
super().__init__(name, logger, opts, srv)
def write_gear(self, value):
self._scale = self.SCALES[self.parameters['value'].datatype.unit] * self.gear
return value
def startModule(self, start_events):
super().startModule(start_events)
start_events.queue(self.read_info)
def check_value(self, value):
"""check if value allows moving in current direction"""
if self._direction > 0:
if value > self.target_limits[1]:
raise BadValueError('above upper limit')
elif value < self.target_limits[0]:
raise BadValueError('below lower limit')
def read_value(self):
pos = self._hw.getPosition(self.axis) * self._scale
if self.isBusy():
try:
self.check_value(pos)
except BadValueError as e:
self._stop()
self._idle_status = ERROR, str(e)
return pos
def read_frequency(self):
return self.io.getFrequency(self.axis)
return self._hw.getFrequency(self.axis)
def write_frequency(self, value):
self.io.setFrequency(self.axis, value)
return self.io.getFrequency(self.axis)
self._hw.setFrequency(self.axis, value)
return self._hw.getFrequency(self.axis)
def read_amplitude(self):
return self.io.getAmplitude(self.axis)
return self._hw.getAmplitude(self.axis)
def write_amplitude(self, value):
self.io.setAmplitude(self.axis, value)
return self.io.getAmplitude(self.axis)
self._hw.setAmplitude(self.axis, value)
return self._hw.getAmplitude(self.axis)
def read_statusbits(self):
self._get_status()
return self.statusbits
def write_tolerance(self, value):
self._hw.setTargetRange(self.axis, value / self._scale)
return value
def _get_status(self):
"""get axis status
- update self.sensor_connected and self.statusbits
- return <moving flag>, <error flag>, <reason>
<moving flag> is True whn moving
<in_error> is True when in error
<reason> is an error text, when in error, 'at target' or '' otherwise
"""
statusbits = self.io.getAxisStatus(self.axis)
self.sensor_connected, self._output, moving, at_target, fwd_stuck, bwd_stuck, error = statusbits
self.statusbits = ''.join(k for k, v in zip('OTFBE', (self._output,) + statusbits[3:]) if v)
if error:
return ERROR, 'other error'
if bwd_stuck:
return ERROR, 'end of travel backward'
if fwd_stuck:
return ERROR, 'end of travel forward'
target_reached = at_target > self._at_target
self._at_target = at_target
if self._moving_since:
if target_reached:
return IDLE, 'at target'
if time.time() < self._moving_since + 0.25:
return BUSY, 'started'
if at_target:
return IDLE, 'at target'
if moving and self._output:
return BUSY, 'moving'
return WARN, 'stopped by unknown reason'
if self._moving_since is False:
return IDLE, 'stopped'
if not self.step_mode and at_target:
return IDLE, 'at target'
return IDLE, ''
def write_output(self, value):
self._hw.setAxisOutput(self.axis, enable=value, autoDisable=0)
return value
def read_status(self):
status = self._get_status()
if self.step_mode:
return self._status
if self._moving_since:
if status[0] != BUSY:
self._moving_since = 0
self.setFastPoll(False)
return status
def _wait(self, delay):
if self._stopped.wait(delay):
raise Stopped()
def _read_pos(self):
if not self.sensor_connected:
return 0
poslist = []
for i in range(9):
if i:
self._wait(0.001)
poslist.append(self.io.getPosition(self.axis) * self._scale)
self._poslist = sorted(poslist)
return self._poslist[len(poslist) // 2] # median
def _run_drive(self, target):
self.value = self._read_pos()
self.status = self._status = BUSY, 'drive by steps'
deadline = time.time() + self.timeout
max_steps = self.maxstep * self.frequency
while True:
for _ in range(2):
dif = target - self.value
steps_per_unit = self.steps_bwd if dif < 0 else self.steps_fwd
tol = max(self.tolerance, 0.6 / steps_per_unit) # avoid a tolerance less than 60% of a step
if abs(dif) > tol * 3:
break
# extra wait time when already close
self._wait(2 * self.delay)
self.read_value()
status = None
if abs(dif) < tol:
status = IDLE, 'in tolerance'
elif self._poslist[2] <= target <= self._poslist[-3]: # target within noise
status = IDLE, 'within noise'
elif dif > 0:
steps = min(max_steps, min(dif, (dif + tol) * self.prop) * steps_per_unit)
else:
steps = max(-max_steps, max(dif, (dif - tol) * self.prop) * steps_per_unit)
if status or steps == 0:
self._status = status
break
if round(steps) == 0: # this should not happen
self._status = WARN, 'steps=0'
break
self._move_steps(steps)
if self._step_size > self.prop * 0.25 / steps_per_unit:
# some progress happened
deadline = time.time() + self.timeout
elif time.time() > deadline:
self._status = WARN, 'timeout - no progress'
break
self.read_status()
def _thread_wrapper(self, func, *args):
try:
func(*args)
except Stopped as e:
self._status = IDLE, str(e)
except Exception as e:
self._status = ERROR, f'{type(e).__name__} - {e}'
finally:
self.io.setAxisOutput(self.axis, enable=0, autoDisable=0)
self.setFastPoll(False)
self._stopped.clear()
self._thread = None
def _stop_thread(self):
if self._thread:
self._stopped.set()
self._thread.join()
def _start_thread(self, *args):
self._stop_thread()
thread = threading.Thread(target=self._thread_wrapper, args=args)
self._thread = thread
thread.start()
def write_target(self, target):
if not self.sensor_connected:
raise HardwareError('no sensor connected')
self._stop_thread()
self.io.setTargetRange(self.axis, self.tolerance / self._scale)
if self.step_mode:
self.status = BUSY, 'changed target'
self._start_thread(self._run_drive, target)
statusbits = self._hw.getAxisStatus(self.axis)
sensor, self.output, moving, attarget, eot_fwd, eot_bwd, sensor_error = statusbits
self.statusbits = ''.join((k for k, v in zip('SOMTFBE', statusbits) if v))
if self._move_steps:
if not (eot_fwd or eot_bwd):
return BUSY, 'moving by steps'
if not sensor:
self._error_state = 'no sensor connected'
elif sensor_error:
self._error_state = 'sensor error'
elif eot_fwd:
self._error_state = 'end of travel forward'
elif eot_bwd:
self._error_state = 'end of travel backward'
else:
self._try_cnt = 0
self.setFastPoll(True, self.fast_interval)
self.io.setTargetPosition(self.axis, target / self._scale)
self.io.setAxisOutput(self.axis, enable=1, autoDisable=0)
self.io.startAutoMove(self.axis, enable=1, relative=0)
self._moving_since = time.time()
self.status = self._get_status()
return target
if self._error_state and not DIRECTION_NAME[self._direction] in self._error_state:
self._error_state = ''
status_text = 'moving' if self._try_count == 0 else 'moving (retry %d)' % self._try_count
if moving and self._history is not None: # history None: moving by steps
self._history.append(self.value)
if len(self._history) < 5:
return BUSY, status_text
beg = self._history.pop(0)
if abs(beg - self.target) < self.tolerance:
# reset normal tolerance
self._stop()
self._idle_status = IDLE, 'in tolerance'
return self._idle_status
# self._hw.setTargetRange(self.axis, self.tolerance / self._scale)
if (self.value - beg) * self._direction > 0:
return BUSY, status_text
self._try_count += 1
if self._try_count < 10:
self.log.warn('no progress retry %d', self._try_count)
return BUSY, status_text
self._idle_status = WARN, 'no progress'
if self._error_state:
self._try_count += 1
if self._try_count < 10 and self._history is not None:
self.log.warn('end of travel retry %d', self._try_count)
self.write_target(self.target)
return Done
self._idle_status = WARN, self._error_state
if self.status[0] != IDLE:
self._stop()
return self._idle_status
@Command()
def stop(self):
if self.step_mode:
self._stop_thread()
self._status = IDLE, 'stopped'
elif self._moving_since:
self._moving_since = False # indicate stop
self.read_status()
def write_target(self, value):
if value == self.read_value():
return value
self.check_limits(value)
self._try_count = 0
self._direction = 1 if value > self.value else -1
# if self._error_state and DIRECTION_NAME[-self._direction] not in self._error_state:
# raise BadValueError('can not move (%s)' % self._error_state)
self._move_steps = 0
self.write_output(1)
# try first with 50 % of tolerance
self._hw.setTargetRange(self.axis, self.tolerance * 0.5 / self._scale)
for itry in range(5):
try:
self._hw.setTargetPosition(self.axis, value / self._scale)
self._hw.startAutoMove(self.axis, enable=1, relative=0)
except Exception as e:
if itry == 4:
raise
self.log.warn('%r', e)
self._history = [self.value]
self._idle_status = IDLE, ''
self.freeze_status(1, BUSY, 'changed target')
self.setFastPoll(True, 1)
return value
@Command(IntRange())
def move(self, steps):
"""relative move by number of steps"""
self._stop_thread()
self.read_value()
if steps > 0:
if self.value > self.target_max:
raise BadValueError('above upper limit')
elif self.value < self.target_min:
raise BadValueError('below lower limit')
self.status = self._status = BUSY, 'moving relative'
self._start_thread(self._run_move, steps)
def _run_move(self, steps):
self.setFastPoll(True, self.fast_interval)
self._move_steps(steps)
self.status = self._status = IDLE, ''
def _move_steps(self, steps):
steps = round(steps)
if not steps:
def doPoll(self):
if self._move_steps == 0:
super().doPoll()
return
previous = self._read_pos()
self.io.setAxisOutput(self.axis, enable=1, autoDisable=0)
# wait for output is really on
for i in range(100):
self._wait(0.001)
self._get_status()
if self._output:
break
else:
raise ValueError('can not switch on output')
for cnt in range(abs(steps)):
self.io.setAxisOutput(self.axis, enable=1, autoDisable=0)
if not self._thread:
raise Stopped('stopped')
self.io.startSingleStep(self.axis, steps < 0)
self._wait(1 / self.frequency)
self._get_status()
if cnt and not self._output:
steps = cnt
break
self._wait(self.delay)
self.value = self._read_pos()
self._step_size = (self.value - previous) / steps
@Command(IntRange(0))
def calib_steps(self, delta):
"""calibrate steps_fwd and steps_bwd using <delta> steps forwards and backwards"""
if not self.sensor_connected:
raise HardwareError('no sensor connected')
self._stop_thread()
self._status = BUSY, 'calibrate step size'
self.read_status()
self._start_thread(self._run_calib, delta)
def _run_calib(self, steps):
self.value = self._read_pos()
if self._calib_range is None or abs(self.target - self.value) > self._calib_range:
self.target = self.value
maxfwd = 0
maxbwd = 0
cntfwd = 0
cntbwd = 0
self._calib_range = 0
for i in range(10):
if self.value <= self.target:
self._status = BUSY, 'move forwards'
self.read_status()
self._move_steps(steps)
while True:
self._move_steps(steps)
if self._step_size and self._output:
maxfwd = max(maxfwd, self._step_size)
cntfwd += 1
if self.value > self.target:
break
else:
self._status = BUSY, 'move backwards'
self.read_status()
self._move_steps(-steps)
while True:
self._move_steps(-steps)
if self._step_size:
maxbwd = max(maxbwd, self._step_size)
cntbwd += 1
if self.value < self.target:
break
# keep track how far we had to go for calibration
self._calib_range = max(self._calib_range, abs(self.value - self.target))
if cntfwd >= 3 and cntbwd >= 3:
self.steps_fwd = 1 / maxfwd
self.steps_bwd = 1 / maxbwd
self._status = IDLE, 'calib step size done'
break
else:
self._status = WARN, 'calib step size failed'
self.read_status()
self._hw.startSingleStep(self.axis, self._direction < 0)
self._move_steps -= self._direction
if self._move_steps % int(self.frequency) == 0: # poll value and status every second
super().doPoll()
@nopoll
def read_info(self):
"""read info from controller"""
axistype = ['linear', 'gonio', 'rotator'][self.io.getActuatorType(self.axis)]
name = self.io.getActuatorName(self.axis)
cap = self.io.measureCapacitance(self.axis) * 1e9
return f'{name} {axistype} {cap:.3g}nF'
cap = self._hw.measureCapacitance(self.axis) * 1e9
axistype = ['linear', 'gonio', 'rotator'][self._hw.getActuatorType(self.axis)]
return '%s %s %.3gnF' % (self._hw.getActuatorName(self.axis), axistype, cap)
def _stop(self):
self._move_steps = 0
self._history = None
for _ in range(5):
try:
self._hw.startAutoMove(self.axis, enable=0, relative=0)
break
except Exception as e:
if itry == 4:
raise
self.log.warn('%r', e)
self._hw.setTargetRange(self.axis, self.tolerance / self._scale)
self.setFastPoll(False)
@Command()
def stop(self):
self._idle_status = IDLE, 'stopped' if self.isBusy() else ''
self._stop()
self.status = self._idle_status
@Command(IntRange())
def move(self, value):
"""relative move by number of steps"""
self._direction = 1 if value > 0 else -1
self.check_value(self.value)
self._history = None
if DIRECTION_NAME[self._direction] in self._error_state:
raise BadValueError('can not move (%s)' % self._error_state)
self._move_steps = value
self._idle_status = IDLE, ''
self.read_status()
self.setFastPoll(True, 1/self.frequency)

279
frappy_psi/bridge.py Normal file
View File

@ -0,0 +1,279 @@
# *****************************************************************************
# 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: Oksana Shliakhtun <oksana.shliakhtun@psi.ch>
# *****************************************************************************
"""Stanford Research Systems SIM900 Mainframe"""
import re
from frappy.core import StringIO, HasIO, Readable, \
Parameter, FloatRange, IntRange, EnumType, \
Property, Attached, IDLE, ERROR, WARN
def string_to_value(value):
"""
Converting the value to float, removing the units, converting the prefix into the number.
:param value: value
:return: float value without units
"""
value_with_unit = re.compile(r'(\d+)([pnumkMG]?)')
value, pfx = value_with_unit.match(value).groups()
pfx_dict = {'p': 1e-12, 'n': 1e-9, 'u': 1e-6, 'm': 1e-3, 'k': 1e3, 'M': 1e6, 'G': 1e9}
if pfx in pfx_dict:
value = round(float(value) * pfx_dict[pfx], 12)
return float(value)
def find_idx(list_of_values, target):
"""
Search for the nearest value and index from the given range for the given target.
:param list_of_values: range of values
:param target: target
:return: closest index and closest value
"""
target = float(target)
cl_idx = None
cl_value = float('inf')
for idx, value in enumerate(list_of_values):
if value >= target:
diff = value - target
if diff < cl_value:
cl_value = value
cl_idx = idx
return cl_idx, cl_value
class BridgeIO(StringIO):
"""_\n is placed at the beginning of each command to distinguish
the previous response with a possible asynchronous response from the actual value returned by the method. """
end_of_line = '\n'
identification = [('_\n*IDN?', r'Stanford_Research_Systems,.*')]
class Base(HasIO):
port = Property('modules port', IntRange(0, 15))
def communicate(self, command):
"""
Connection to the particular module x.
:param command: command
:return: return
"""
return self.io.communicate(f'_\nconn {self.port:x},"_\n"\n{command}')
def query(self, command):
"""converting to float"""
return float(self.communicate(command))
class Resistance(Base, Readable):
value = Parameter('resistance', datatype=FloatRange, unit='ohm')
output_offset = Parameter('resistance deviation', datatype=FloatRange, unit='Ohm', readonly=False)
phase_hold = Parameter('phase hold', EnumType('phase hold', off=0, on=1))
RES_RANGE = ['20mOhm', '200mOhm', '2Ohm', '20Ohm', '200Ohm', '2kOhm', '20kOhm', '200kOhm',
'2MOhm', '20MOhm']
irange = Parameter('resistance range index', EnumType('resistance range index',
{name: idx for idx, name in enumerate(RES_RANGE)}),
readonly=False)
range = Parameter('resistance range value', FloatRange(2e-2, 2e7), unit='Om', readonly=False)
TIME_CONST = ['0.3s', '1s', '3s', '10s', '30s', '100s', '300s']
itc = Parameter('time constant index',
EnumType('time const. index range',
{name: value for value, name in enumerate(TIME_CONST)}), readonly=False)
tc = Parameter('time constant value', FloatRange(1e-1, 3e2), unit='s', readonly=False)
EXCT_RANGE = ['0', '3uV', '10uV', '30uV', '100uV', '300uV', '1mV', '3mV', '10mV', '30mV']
iexct = Parameter('excitation index',
EnumType('excitation index range', {name: idx for idx, name in enumerate(EXCT_RANGE, start=-1)}),
readonly=False)
exct = Parameter('excitation value', FloatRange(0, 3e-2), unit='s', default=300, readonly=False)
autorange = Parameter('autorange_on', EnumType('autorange', off=0, on=1),
readonly=False, default=0)
RES_RANGE_values = [string_to_value(value) for value in RES_RANGE]
TIME_CONST_values = [string_to_value(value) for value in TIME_CONST]
EXCT_RANGE_values = [string_to_value(value) for value in EXCT_RANGE]
ioClass = BridgeIO
def doPoll(self):
super().doPoll()
max_res = abs(self.value)
if self.autorange == 1:
if max_res >= 0.9 * self.range and self.irange < 9:
self.write_irange(self.irange + 1)
elif max_res <= 0.3 * self.range and self.irange > 0:
self.write_irange(self.irange - 1)
def read_status(self):
"""
Both the mainframe (SR900) and the module (SR921) have the same commands for the status,
here implemented commands are made for module status, not the frame!
:return: status type and message
"""
esr = int(self.communicate('*esr?')) # standart event status byte
ovsr = int(self.communicate('ovsr?')) # overload status
cesr = int(self.communicate('cesr?')) # communication error status
if esr & (1 << 1):
return ERROR, 'input error, cleared'
if esr & (1 << 2):
return ERROR, 'query error'
if esr & (1 << 4):
return ERROR, 'execution error'
if esr & (1 << 5):
return ERROR, 'command error'
if cesr & (1 << 0):
return ERROR, 'parity error'
if cesr & (1 << 2):
return ERROR, 'noise error'
if cesr & (1 << 4):
return ERROR, 'input overflow, cleared'
if cesr & (1 << 3):
return ERROR, 'hardware overflow'
if ovsr & (1 << 0):
return ERROR, 'output overload'
if cesr & (1 << 7):
return WARN, 'device clear'
if ovsr & (1 << 2):
return WARN, 'current saturation'
if ovsr & (1 << 3):
return WARN, 'under servo'
if ovsr & (1 << 4):
return WARN, 'over servo'
return IDLE, ''
def read_value(self):
return self.query('rval?')
def read_irange(self):
"""index of the resistance value according to the range"""
return self.query('rang?')
def write_irange(self, idx):
value = int(idx)
self.query(f'rang {value}; rang?')
self.read_range()
return value
def read_range(self):
"""value of the resistance range"""
idx = self.read_irange()
name = self.RES_RANGE[idx]
return string_to_value(name)
def write_range(self, target):
cl_idx, cl_value = find_idx(self.RES_RANGE_values, target)
self.query(f'rang {cl_idx}; rang?')
return cl_value
def read_output_offset(self):
"""Output offset, can be set by user. This is the value subtracted from the measured value"""
return self.query('rset?')
def write_output_offset(self, output_offset):
self.query(f'rset {output_offset};rset?')
def read_itc(self):
"""index of the temperature constant value according to the range"""
return self.query('tcon?')
def write_itc(self, itc):
self.read_itc()
value = int(itc)
return self.query(f'tcon {value}; tcon?')
def read_tc(self):
idx = self.read_itc()
name = self.TIME_CONST[idx]
return string_to_value(name)
def write_tc(self, target):
cl_idx, cl_value = find_idx(self.TIME_CONST_values, target)
self.query(f'tcon {cl_idx};tcon?')
return cl_value
def read_autorange(self):
return self.autorange
def write_autorange(self, value):
self.query(f'agai {value:d};agai?')
return value
def read_iexct(self):
"""index of the excitation value according to the range"""
return int(self.query('exci?'))
def write_iexct(self, iexct):
value = int(iexct)
return self.query(f'exci {value};exci?')
def write_exct(self, target):
target = float(target)
cl_idx = None
cl_value = float('inf')
min_diff = float('inf')
for idx, value in enumerate(self.EXCT_RANGE_values):
diff = abs(value - target)
if diff < min_diff:
min_diff = diff
cl_value = value
cl_idx = idx
self.write_iexct(cl_idx)
return cl_value
def read_exct(self):
idx = int(self.read_iexct())
name = self.EXCT_RANGE[idx + 1]
return string_to_value(name)
def read_phase_hold(self):
"""
Set the phase hold mode (if on - phase is assumed to be zero).
:return: 0 - off, 1 - on
"""
return int(self.communicate('phld?'))
def write_phase_hold(self, phase_hold):
self.communicate(f'phld {phase_hold}')
return self.read_phase_hold()
class Phase(Readable):
resistance = Attached()
value = Parameter('phase', FloatRange, default=0, unit='deg')
def read_value(self):
return self.resistance.query('phas?')
class Deviation(Readable):
resistance = Attached()
value = Parameter('resistance deviation', FloatRange(), unit='Ohm')
def read_value(self):
return self.resistance.query('rdev?')

View File

@ -1,576 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# *****************************************************************************
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation; either version 2 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc.,
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# Module authors:
# Markus Zolliker <markus.zolliker@psi.ch>
# *****************************************************************************
"""Software calibration"""
import os
import re
from os.path import basename, dirname, exists, join
import numpy as np
from scipy.interpolate import PchipInterpolator, CubicSpline, PPoly # pylint: disable=import-error
from frappy.errors import ProgrammingError, RangeError
from frappy.lib import clamp
to_scale = {
'lin': lambda x: x,
'log': lambda x: np.log10(x),
}
from_scale = {
'lin': lambda x: x,
'log': lambda x: 10 ** np.array(x),
}
TYPES = [ # lakeshore type, inp-type, loglog
('DT', 'si', False), # Si diode
('TG', 'gaalas', False), # GaAlAs diode
('PT', 'pt250', False), # platinum, 250 Ohm range
('PT', 'pt500', False), # platinum, 500 Ohm range
('PT', 'pt2500', False), # platinum, 2500 Ohm range
('RF', 'rhfe', False), # rhodium iron
('CC', 'c', True), # carbon, LakeShore acronym unknown
('CX', 'cernox', True), # Cernox
('RX', 'ruox', True), # rutheniumm oxide
('GE', 'ge', True), # germanium, LakeShore acronym unknown
]
OPTION_TYPE = {
'loglog': 0, # boolean
'extrange': 2, # tuple(min T, max T for extrapolation
'calibrange': 2, # tuple(min T, max T)
}
class HasOptions:
def insert_option(self, key, value):
key = key.strip()
argtype = OPTION_TYPE.get(key, 1)
if argtype == 1: # one number or string
try:
value = float(value)
except ValueError:
value = value.strip()
elif argtype == 0:
if value.strip().lower() in ('false', '0'):
value = False
else:
value = True
else:
value = [float(f) for f in value.split(',')]
self.options[key] = value
class StdParser(HasOptions):
"""parser used for reading columns"""
def __init__(self, **options):
"""keys of options may be either 'x' or 'logx' and either 'y' or 'logy'
default is x=0, y=1
"""
if 'logx' in options:
self.xscale = 'log'
self.xcol = options.pop('logx')
else:
self.xscale = 'lin'
self.xcol = options.pop('x', 0)
if 'logy' in options:
self.yscale = 'log'
self.ycol = options.pop('logy')
else:
self.yscale = 'lin'
self.ycol = options.pop('y', 1)
self.xdata, self.ydata = [], []
self.options = options
self.invalid_lines = []
def parse(self, line):
"""get numbers from a line and put them to self.xdata / self.ydata"""
row = line.split()
try:
self.xdata.append(float(row[self.xcol]))
self.ydata.append(float(row[self.ycol]))
except (IndexError, ValueError):
self.invalid_lines.append(line)
return
def finish(self):
pass
class InpParser(StdParser):
"""M. Zollikers *.inp calcurve format"""
HEADERLINE = re.compile(r'#?(?:(\w+)\s*=\s*([^!# \t\n]*)|curv.*)')
INP_TYPES = {ityp: (ltyp, loglog) for ltyp, ityp, loglog in TYPES}
def __init__(self, **options):
options.update(x=0, y=1)
super().__init__(**options)
self.header = True
def parse(self, line):
"""scan header"""
if self.header:
match = self.HEADERLINE.match(line)
if match:
key, value = match.groups()
if key is None:
self.header = False
else:
key = key.lower()
value = value.strip()
if key == 'type':
type_, loglog = self.INP_TYPES.get(value.lower(), (None, None))
if type_ is not None:
self.options['type'] = type_
if loglog is not None:
self.options['loglog'] = loglog
else:
self.insert_option(key, value)
return
elif line.startswith('!'):
return
super().parse(line)
class Parser340(StdParser):
"""parser for LakeShore *.340 files"""
HEADERLINE = re.compile(r'([^:]*):\s*([^(]*)')
CALIBHIGH = dict(L=325, M=420, H=500, B=40)
def __init__(self, **options):
options.update(x=1, y=2)
super().__init__(**options)
self.header = True
def parse(self, line):
"""scan header"""
if self.header:
match = self.HEADERLINE.match(line)
if match:
key, value = match.groups()
key = ''.join(key.split()).lower()
value = value.strip()
if key == 'dataformat':
if value[0:1] == '4':
self.xscale, self.yscale = 'log', 'lin' # logOhm
self.options['loglog'] = True
elif value[0:1] == '5':
self.xscale, self.yscale = 'log', 'log' # logOhm, logK
self.options['loglog'] = True
elif value[0:1] in ('1', '2', '3'):
self.options['loglog'] = False
else:
raise ValueError('invalid Data Format')
self.options['scale'] = self.xscale + self.yscale
self.insert_option(key, value)
return
if 'No.' in line:
self.header = False
return
if len(line.split()) != 3:
return
super().parse(line)
if self.header and self.xdata:
# valid line detected
self.header = False
def finish(self):
model = self.options.get('sensormodel', '').split('-')
if model[0]:
self.options['type'] = model[0]
if 'calibrange' not in self.options:
if len(model) > 2:
try: # e.g. model[-1] == 1.4M -> calibrange = 1.4, 420
self.options['calibrange'] = float(model[-1][:-1]), self.CALIBHIGH[model[-1][-1]]
return
except (ValueError, KeyError):
pass
class CaldatParser(StdParser):
"""parser for format from sea/tcl/startup/calib_ext.tcl"""
def __init__(self, options):
options.update(x=1, y=2)
super().__init__(options)
PARSERS = {
"340": Parser340,
"inp": InpParser,
"caldat": CaldatParser,
"dat": StdParser, # lakeshore raw data *.dat format
}
def check(x, y, islog):
# check interpolation error
yi = y[:-2] + (x[1:-1] - x[:-2]) * (y[2:] - y[:-2]) / (x[2:] - x[:-2])
if islog:
return sum((yi - y[1:-1]) ** 2)
return sum((np.log10(yi) - np.log10(y[1:-1])) ** 2)
def get_curve(newscale, curves):
"""get curve from curve cache (converts not existing ones)
:param newscale: the new scale to get
:param curves: a dict <scale> of <array> storing available scales
:return: the retrieved or converted curve
"""
if newscale in curves:
return curves[newscale]
for scale, array in curves.items():
curves[newscale] = curve = to_scale[newscale](from_scale[scale](array))
return curve
class CalCurve(HasOptions):
EXTRAPOLATION_AMOUNT = 0.1
MAX_EXTRAPOLATION_FACTOR = 2
def __init__(self, calibspec=None, *, x=None, y=None, cubic_spline=True, **options):
"""calibration curve
:param calibspec: a string with name or filename, options
lookup path for files in env. variable FRAPPY_CALIB_PATH
calibspec format:
[<full path> | <name>][,<key>=<value> ...]
for <key>/<value> as in parser arguments
:param x, y: x and y arrays (given instead of calibspec)
:param cubic_split: set to False for always using Pchip interpolation
:param options: options for parsers
"""
self.options = options
if calibspec is None:
parser = StdParser()
parser.xdata = x
parser.ydata = y
else:
if x or y:
raise ProgrammingError('can not give both calibspec and x,y ')
sensopt = calibspec.split(',')
calibname = sensopt.pop(0)
_, dot, ext = basename(calibname).rpartition('.')
kind = None
pathlist = os.environ.get('FRAPPY_CALIB_PATH', '').split(':')
pathlist.append(join(dirname(__file__), 'calcurves'))
for path in pathlist:
# first try without adding kind
filename = join(path.strip(), calibname)
if exists(filename):
kind = ext if dot else None
break
# then try adding all kinds as extension
for nam in calibname, calibname.upper(), calibname.lower():
for kind in PARSERS:
filename = join(path.strip(), '%s.%s' % (nam, kind))
if exists(filename):
break
else:
continue
break
else:
continue
break
else:
raise FileNotFoundError(calibname)
sensopt = iter(sensopt)
for opt in sensopt:
key, _, value = opt.lower().partition('=')
if OPTION_TYPE.get(key) == 2:
self.options[key] = float(value), float(next(sensopt))
else:
self.insert_option(key, value)
kind = self.options.pop('kind', kind)
cls = PARSERS.get(kind, StdParser)
try:
parser = cls(**self.options)
with open(filename, encoding='utf-8') as f:
for line in f:
parser.parse(line)
parser.finish()
except Exception as e:
raise ValueError('error parsing calib curve %s %r' % (calibspec, e)) from e
# take defaults from parser options
self.options = dict(parser.options, **self.options)
x = np.asarray(parser.xdata)
y = np.asarray(parser.ydata)
if len(x) < 2:
raise ValueError('calib file %s has less than 2 points' % calibspec)
if x[0] > x[-1]:
x = np.flip(np.array(x))
y = np.flip(np.array(y))
else:
x = np.array(x)
y = np.array(y)
not_incr_idx = np.argwhere(x[1:] <= x[:-1])
if len(not_incr_idx):
raise RangeError('x not monotonic at x=%.4g' % x[not_incr_idx[0]])
self.x = {parser.xscale: x}
self.y = {parser.yscale: y}
self.lin_forced = [parser.yscale == 'lin' and (y[0] <= 0 or y[-1] <= 0),
parser.xscale == 'lin' and x[0] <= 0]
if sum(self.lin_forced):
self.loglog = False
else:
self.loglog = self.options.get('loglog', y[0] > y[-1]) # loglog defaults to True for NTC
newscale = 'log' if self.loglog else 'lin'
self.scale = newscale
x = get_curve(newscale, self.x)
y = get_curve(newscale, self.y)
self.convert_x = to_scale[newscale]
self.convert_y = from_scale[newscale]
self.calibrange = self.options.get('calibrange')
dirty = set()
self.extra_points = False
self.cutted = False
if self.calibrange:
self.calibrange = sorted(self.calibrange)
# determine indices (ibeg, iend) of first and last well calibrated point
ylin = get_curve('lin', self.y)
beg, end = self.calibrange
if y[0] > y[-1]:
ylin = -ylin
beg, end = -end, -beg
ibeg, iend = np.searchsorted(ylin, (beg, end))
if ibeg > 0 and abs(ylin[ibeg-1] - beg) < 0.1 * (ylin[ibeg] - ylin[ibeg-1]):
# add previous point, if close
ibeg -= 1
if iend < len(ylin) and abs(ylin[iend] - end) < 0.1 * (ylin[iend] - ylin[iend-1]):
# add next point, if close
iend += 1
if self.options.get('cut', False):
self.cutted = True
x = x[ibeg:iend]
y = y[ibeg:iend]
self.x = {newscale: x}
self.y = {newscale: y}
ibeg = 0
iend = len(x)
dirty.add('xy')
else:
self.extra_points = ibeg, len(x) - iend
else:
ibeg = 0
iend = len(x)
ylin = get_curve('lin', self.y)
self.calibrange = tuple(sorted([ylin[0], ylin[-1]]))
if cubic_spline:
# fit well calibrated part with spline
# determine slopes of calibrated part with CubicSpline
spline = CubicSpline(x[ibeg:iend], y[ibeg:iend])
roots = spline.derivative().roots(extrapolate=False)
if len(roots):
cubic_spline = False
self.cubic_spline = cubic_spline
if cubic_spline:
coeff = spline.c
if self.extra_points:
p = PchipInterpolator(x, y).c
# use Pchip outside left and right of calibrange
# remark: first derivative at end of calibrange is not continuous
coeff = np.concatenate((p[:, :ibeg], coeff, p[:, iend-1:]), axis=1)
else:
spline = PchipInterpolator(x, y)
coeff = spline.c
# extrapolation extension
# linear extrapolation is more robust than spline extrapolation
x1, x2 = x[0], x[-1]
# take slope at end of calibrated range for extrapolation
slopes = spline([x[ibeg], x[iend-1]], 1)
for i, j in enumerate([ibeg, iend-2]):
# slope of last interval in calibrange
si = (y[j+1] - y[j])/(x[j+1] - x[j])
# make sure slope is not more than a factor 2 different
# from the slope calculated at the outermost calibrated intervals
slopes[i] = clamp(slopes[i], 2*si, 0.5 * si)
dx = 0.1 if self.loglog else (x2 - x1) * 0.1
xe = np.concatenate(([x1 - dx], x, [x2 + dx]))
# x3 = np.append(x, x2 + dx)
# y3 = np.append(y, y[-1] + slope * dx)
y0 = y[0] - slopes[0] * dx
coeff = np.concatenate(([[0], [0], [slopes[0]], [y0]], coeff, [[0], [0], [slopes[1]], [y[-1]]]), axis=1)
self.spline = PPoly(coeff, xe)
# ranges without extrapolation:
self.xrange = get_curve('lin', self.x)[[0, -1]]
self.yrange = sorted(get_curve('lin', self.y)[[0, -1]])
self.calibrange = [max(self.calibrange[0], self.yrange[0]),
min(self.calibrange[1], self.yrange[1])]
self.set_extrapolation()
# check
# ys = self.spline(xe)
# ye = np.concatenate(([y0], y, [y[-1] + slope2 * dx]))
# assert np.all(np.abs(ys - ye) < 1e-5 * (0.1 + np.abs(ys + ye)))
def set_extrapolation(self, extleft=None, extright=None):
"""set default extrapolation range for export method
:param extleft: y value for the lower end of the extrapolation
:param extright: y value for the upper end of the extrapolation
if arguments omitted or None are replaced by a default extrapolation scheme
on return self.extx and self.exty are set to the extrapolated ranges
"""
yc1, yc2 = self.calibrange
y1, y2 = to_scale[self.scale]([yc1, yc2])
d = (y2 - y1) * self.EXTRAPOLATION_AMOUNT
yex1, yex2 = tuple(from_scale[self.scale]([y1 - d, y2 + d]))
t1, t2 = tuple(from_scale[self.scale]([y1, y2]))
# raw units, excluding extrapolation points at end
xrng = self.spline.x[1], self.spline.x[-2]
# first and last point
yp1, yp2 = sorted(from_scale[self.scale](self.spline(xrng)))
xrng = from_scale[self.scale](xrng)
# limit by maximal factor
f = self.MAX_EXTRAPOLATION_FACTOR
# but ext range should be at least to the points in curve
self.exty = [min(yp1, max(yex1, min(t1 / f, t1 * f))),
max(yp2, min(yex2, max(t2 * f, t2 / f)))]
if extleft is not None:
self.exty[0] = min(extleft, yp1)
if extright is not None:
self.exty[1] = max(extright, yp2)
self.extx = sorted(self.invert(*yd) for yd in zip(self.exty, xrng))
# check that sensor range is not extended by more than a factor f
extnew = [max(self.extx[0], min(xrng[0] / f, xrng[0] * f)),
min(self.extx[1], max(xrng[1] / f, xrng[1] * f))]
if extnew != self.extx:
# need further reduction
self.extx = extnew
self.exty = sorted(self(extnew))
def convert(self, value):
"""convert a single value
return a tuple (converted value, boolean: was it clamped?)
"""
x = clamp(value, *self.extx)
return self(x), x == value
def __call__(self, value):
"""convert value or numpy array without checking extrapolation range"""
return self.convert_y(self.spline(self.convert_x(value)))
def invert(self, y, defaultx=None, xscale=True, yscale=True):
"""invert y, return defaultx if no solution is found"""
if yscale:
y = to_scale[self.scale](y)
r = self.spline.solve(y)
try:
if xscale:
return from_scale[self.scale](r[0])
return r[0]
except IndexError:
return defaultx
def export(self, logformat=False, nmax=199, yrange=None, extrapolate=True, xlimits=None):
"""export curve for downloading to hardware
:param nmax: max number of points. if the number of given points is bigger,
the points with the lowest interpolation error are omitted
:param logformat: a list with two elements of None, True or False
True: use log, False: use line, None: use log if self.loglog
values None are replaced with the effectively used format
False / True are replaced by [False, False] / [True, True]
default is False
:param yrange: to reduce or extrapolate to this interval (extrapolate is ignored when given)
:param extrapolate: a flag indicating whether the curves should be extrapolated
to the preset extrapolation range
:param xlimits: max x range
:return: numpy array with 2 dimensions returning the curve
"""
if logformat in (True, False):
logformat = [logformat, logformat]
try:
scales = []
for idx, logfmt in enumerate(logformat):
if logfmt and self.lin_forced[idx]:
raise ValueError('%s must contain positive values only' % 'xy'[idx])
logformat[idx] = linlog = self.loglog if logfmt is None else logfmt
scales.append('log' if linlog else 'lin')
xscale, yscale = scales
except (TypeError, AssertionError):
raise ValueError('logformat must be a 2 element list or a boolean')
x = self.spline.x[1:-1] # raw units, excluding extrapolated points
x1, x2 = xmin, xmax = x[0], x[-1]
y1, y2 = sorted(self.spline([x1, x2]))
if extrapolate and not yrange:
yrange = self.exty
if yrange is not None:
xmin, xmax = sorted(self.invert(*yd, xscale=False) for yd in zip(yrange, [x1, x2]))
if xlimits is not None:
lim = to_scale[self.scale](xlimits)
xmin = clamp(xmin, *lim)
xmax = clamp(xmax, *lim)
if xmin != x1 or xmax != x2:
ibeg, iend = np.searchsorted(x, (xmin, xmax))
if abs(x[ibeg] - xmin) < 0.1 * (x[ibeg + 1] - x[ibeg]):
# remove first point, if close
ibeg += 1
if abs(x[iend - 1] - xmax) < 0.1 * (x[iend - 1] - x[iend - 2]):
# remove last point, if close
iend -= 1
x = np.concatenate(([xmin], x[ibeg:iend], [xmax]))
y = self.spline(x)
# convert to exported scale
if xscale != self.scale:
x = to_scale[xscale](from_scale[self.scale](x))
if yscale != self.scale:
y = to_scale[yscale](from_scale[self.scale](y))
# reduce number of points, if needed
n = len(x)
i, j = 1, n - 1 # index range for calculating interpolation deviation
deviation = np.zeros(n)
while True:
# calculate interpolation error when a single point is omitted
ym = y[i-1:j-1] + (x[i:j] - x[i-1:j-1]) * (y[i+1:j+1] - y[i-1:j-1]) / (x[i+1:j+1] - x[i-1:j-1])
if yscale == 'log':
deviation[i:j] = np.abs(ym - y[i:j])
else:
deviation[i:j] = np.abs(ym - y[i:j]) / (np.abs(ym + y[i:j]) + 1e-10)
if n <= nmax:
break
idx = np.argmin(deviation[1:-1]) + 1 # find index of the smallest error
y = np.delete(y, idx)
x = np.delete(x, idx)
deviation = np.delete(deviation, idx)
n -= 1
# index range to recalculate
i, j = max(1, idx - 1), min(n - 1, idx + 1)
self.deviation = deviation # for debugging purposes
return np.stack([x, y], axis=1)

View File

@ -1,313 +0,0 @@
libpath = '/home/l_samenv/frappy/cetoniSDK/CETONI_SDK_Raspi_64bit_v20220627/python/src/'
import sys
if libpath not in sys.path:
sys.path.append(libpath)
from frappy.core import Drivable, Readable, StringIO, HasIO, FloatRange, IntRange, StringType, BoolType, EnumType, \
Parameter, Property, PersistentParam, Command, IDLE, BUSY, ERROR, WARN, Attached, Module
from qmixsdk import qmixbus
from qmixsdk import qmixpump
from qmixsdk import qmixvalve
from qmixsdk.qmixpump import ContiFlowProperty, ContiFlowSwitchingMode
from qmixsdk.qmixbus import UnitPrefix, TimeUnit
import time
class LabCannBus(Module):
deviceconfig = Property('config files', StringType(),default="/home/l_samenv/frappy/cetoniSDK/CETONI_SDK_Raspi_64bit_v20220627/config/dual_pumps")
def earlyInit(self):
super().earlyInit()
self.bus = qmixbus.Bus()
self.bus.open(self.deviceconfig, "")
def initModule(self):
super().initModule()
self.bus.start()
with open('/sys/class/ionopimax/buzzer/beep', 'w') as f :
f.write('200 50 3')
def shutdownModule(self):
"""Close the connection"""
self.bus.stop()
self.bus.close()
class SyringePump(Drivable):
io = Attached()
pump_name = Property('name of pump', StringType(),default="Nemesys_S_1_Pump")
valve_name = Property('name of valve', StringType(),default="Nemesys_S_1_Valve")
inner_diameter_set = Property('inner diameter', FloatRange(), default=1)
piston_stroke_set = Property('piston stroke', FloatRange(), default=60)
value = Parameter('volume', FloatRange(unit='uL'))
status = Parameter()
max_flow_rate = Parameter('max flow rate', FloatRange(0,100000, unit='uL/s',), readonly=True)
max_volume = Parameter('max volume', FloatRange(0,100000, unit='uL',), readonly=True)
target_flow_rate = Parameter('target flow rate', FloatRange(unit='uL/s'), readonly=False)
real_flow_rate = Parameter('actual flow rate', FloatRange(unit='uL/s'), readonly=True)
target = Parameter('target volume', FloatRange(unit='uL'), readonly=False)
no_of_valve_pos = Property('number of valve positions', IntRange(0,10), default=1)
valve_pos = Parameter('valve position', EnumType('valve', CLOSED=0, APP=1, RES=2, OPEN=3), readonly=False)
force = Parameter('syringe force', FloatRange(unit='kN'), readonly=True)
max_force = Parameter('max device force', FloatRange(unit='kN'), readonly=True)
force_limit = Parameter('user force limit', FloatRange(unit='kN'), readonly=False)
_resolving_force_overload = False
def initModule(self):
super().initModule()
self.pump = qmixpump.Pump()
self.pump.lookup_by_name(self.pump_name)
self.valve = qmixvalve.Valve()
self.valve.lookup_by_name(self.valve_name)
def initialReads(self):
if self.pump.is_in_fault_state():
self.pump.clear_fault()
if not self.pump.is_enabled():
self.pump.enable(True)
self.pump.set_syringe_param(self.inner_diameter_set, self.piston_stroke_set)
self.pump.set_volume_unit(qmixpump.UnitPrefix.micro, qmixpump.VolumeUnit.litres)
self.pump.set_flow_unit(qmixpump.UnitPrefix.micro, qmixpump.VolumeUnit.litres, qmixpump.TimeUnit.per_second)
self.max_flow_rate = round(self.pump.get_flow_rate_max(),2)
self.max_volume = round(self.pump.get_volume_max(),2)
self.valve_pos = self.valve.actual_valve_position()
self.target_flow_rate = round(self.max_flow_rate * 0.5,2)
self.target = max(0, round(self.pump.get_fill_level(),2))
self.pump.enable_force_monitoring(True)
self.max_force = self.pump.get_max_device_force()
self.force_limit = self.max_force
def read_value(self):
return round(self.pump.get_fill_level(),2)
def write_target(self, target):
if self.read_valve_pos() == 0 :
self.status = ERROR, 'Cannot pump if valve is closed'
self.log.warn('Cannot pump if valve is closed')
return target
else:
self.pump.set_fill_level(target, self.target_flow_rate)
self.status = BUSY, 'Target changed'
self.log.info(f'Started pumping at {self.target_flow_rate} ul/s')
return target
def write_target_flow_rate(self, rate):
self.target_flow_rate = rate
return rate
def read_real_flow_rate(self):
return round(self.pump.get_flow_is(),2)
def read_valve_pos(self):
return self.valve.actual_valve_position()
def write_valve_pos(self, target_pos):
self.valve.switch_valve_to_position(target_pos)
return target_pos
def read_force(self):
return round(self.pump.read_force_sensor(),3)
def read_force_limit(self):
return self.pump.get_force_limit()
def write_force_limit(self, limit):
self.pump.write_force_limit(limit)
return limit
def read_status(self):
fault_state = self.pump.is_in_fault_state()
pumping = self.pump.is_pumping()
pump_enabled = self.pump.is_enabled()
safety_stop_active = self.pump.is_force_safety_stop_active()
if fault_state == True:
return ERROR, 'Pump in fault state'
elif self._resolving_force_overload :
return BUSY, 'Resolving force overload'
elif safety_stop_active:
return ERROR, 'Pressure safety stop'
elif not pump_enabled:
return ERROR, 'Pump not enabled'
elif pumping == True:
return BUSY, f'Pumping {self.real_flow_rate} ul/s'
elif self.read_valve_pos() == 0:
return IDLE, 'Valve closed'
else:
return IDLE, ''
@Command
def stop(self):
self.pump.stop_pumping()
self.target = self.pump.get_fill_level()
self.status = BUSY, 'Stopping'
@Command
def clear_errors(self):
"""Clear fault state and enable pump"""
if self.pump.is_in_fault_state():
self.pump.clear_fault()
self.log.info('Cleared faults')
if not self.pump.is_enabled():
self.pump.enable(True)
self.log.info('Pump was disabled, re-enabling')
self.target = max(0,round(self.value,2))
self.status = IDLE, ''
@Command
def resolve_force_overload(self):
"""Resolve a force overload situation"""
if not self.pump.is_force_safety_stop_active():
self.status = ERROR, 'No force overload detected'
self.log.warn('No force overload to be resolved')
return
self._resolving_force_overload = True
self.status = BUSY, 'Resolving force overload'
self.pump.enable_force_monitoring(False)
flow = 0 - self.pump.get_flow_rate_max() / 100
self.pump.generate_flow(flow)
safety_stop_active = False
while not safety_stop_active:
time.sleep(0.1)
safety_stop_active = self.pump.is_force_safety_stop_active()
self.pump.stop_pumping()
self.pump.enable_force_monitoring(True)
time.sleep(0.3)
self._resolving_force_overload = False
self.status = self.read_status()
class ContiFlowPump(Drivable):
io = Attached()
inner_diameter_set = Property('inner diameter', FloatRange(), default=1)
piston_stroke_set = Property('piston stroke', FloatRange(), default=60)
crossflow_seconds = Property('crossflow duration', FloatRange(unit='s'),default=2)
value = PersistentParam('flow rate', FloatRange(unit='uL/s'))
status = PersistentParam()
max_refill_flow = Parameter('max refill flow', FloatRange(unit='uL/s'), readonly=True)
refill_flow = Parameter('refill flow', FloatRange(unit='uL/s'), readonly=False)
max_flow_rate = Parameter('max flow rate', FloatRange(0,100000, unit='uL/s',), readonly=True)
target = Parameter('target flow rate', FloatRange(unit='uL/s'), readonly=False)
def initModule(self):
super().initModule()
self.pump = qmixpump.ContiFlowPump()
self.pump.lookup_by_name("ContiFlowPump_1")
def initialReads(self):
if self.pump.is_in_fault_state():
self.pump.clear_fault()
if not self.pump.is_enabled():
self.pump.enable(True)
self.syringe_pump1 = self.pump.get_syringe_pump(0)
self.syringe_pump1.set_syringe_param(self.inner_diameter_set, self.piston_stroke_set)
self.syringe_pump2 = self.pump.get_syringe_pump(1)
self.syringe_pump2.set_syringe_param(self.inner_diameter_set, self.piston_stroke_set)
self.pump.set_volume_unit(qmixpump.UnitPrefix.micro, qmixpump.VolumeUnit.litres)
self.pump.set_flow_unit(qmixpump.UnitPrefix.micro, qmixpump.VolumeUnit.litres, qmixpump.TimeUnit.per_second)
self.pump.set_device_property(ContiFlowProperty.SWITCHING_MODE, ContiFlowSwitchingMode.CROSS_FLOW)
self.max_refill_flow = self.pump.get_device_property(ContiFlowProperty.MAX_REFILL_FLOW)
self.pump.set_device_property(ContiFlowProperty.REFILL_FLOW, self.max_refill_flow / 2.0)
self.pump.set_device_property(ContiFlowProperty.CROSSFLOW_DURATION_S, self.crossflow_seconds)
self.pump.set_device_property(ContiFlowProperty.OVERLAP_DURATION_S, 0)
self.max_flow_rate = self.pump.get_flow_rate_max()
self.target = 0
def read_value(self):
return round(self.pump.get_flow_is(),3)
def write_target(self, target):
if target <= 0:
self.pump.stop_pumping()
self.status = self.read_status()
return 0
else:
self.pump.generate_flow(target)
self.status = BUSY, 'Target changed'
return target
def read_refill_flow(self):
return round(self.pump.get_device_property(ContiFlowProperty.REFILL_FLOW),3)
def write_refill_flow(self, refill_flow):
self.pump.set_device_property(ContiFlowProperty.REFILL_FLOW, refill_flow)
self.max_flow_rate = self.pump.get_flow_rate_max()
return refill_flow
def read_status(self):
fault_state = self.pump.is_in_fault_state()
pumping = self.pump.is_pumping()
pump_enabled = self.pump.is_enabled()
pump_initialised = self.pump.is_initialized()
pump_initialising = self.pump.is_initializing()
if fault_state == True:
return ERROR, 'Pump in fault state'
elif not pump_enabled:
return ERROR, 'Pump not enabled'
elif not pump_initialised:
return WARN, 'Pump not initialised'
elif pump_initialising:
return BUSY, 'Pump initialising'
elif pumping == True:
return BUSY, 'Pumping'
else:
return IDLE, ''
@Command
def stop(self):
self.pump.stop_pumping()
self.target = 0
self.status = BUSY, 'Stopping'
@Command
def clear_errors(self):
"""Clear fault state and enable pump"""
if self.pump.is_in_fault_state():
self.pump.clear_fault()
self.log.info('Cleared faults')
if not self.pump.is_enabled():
self.pump.enable(True)
self.log.info('Pump was disabled, re-enabling')
self.target = 0
self.status = IDLE, ''
@Command
def initialise(self):
"""Initialise the ConfiFlow pump"""
self.pump.initialize()

View File

@ -1,123 +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:
# Markus Zolliker <markus.zolliker@psi.ch>
# *****************************************************************************
import time
import math
from frappy.core import Parameter, FloatRange, IntRange, Property
from frappy.errors import ProgrammingError
class FrozenParam(Parameter):
"""workaround for lazy hardware
Some hardware does not react nicely: when a parameter is changed,
and read back immediately, still the old value is returned.
This special parameter helps fixing this problem.
Mechanism:
- after a call to write_<param> for a short time (<n_polls> * <interval>)
the hardware is polled until the readback changes before the 'changed'
message is replied to the client
- if there is no change yet within short time, the 'changed' message is
set with the given value and further calls to read_<param> return also
this given value until the readback value has changed or until
<timeout> sec have passed.
For float parameters, the behaviour for small changes is improved
when the write_<param> method tries to return the (may be rounded) value,
as if it would be returned by the hardware. If this behaviour is not
known, or the programmer is too lazy to implement it, write_<param>
should return None or the given value.
Also it will help to adjust the datatype properties
'absolute_resolution' and 'relative_resolution' to reasonable values.
"""
timeout = Property('timeout for freezing readback value',
FloatRange(0, unit='s'), default=30)
n_polls = Property("""number polls within write method""",
IntRange(0), default=1)
interval = Property("""interval for polls within write method
the product n_polls * interval should not be more than a fraction of a second
in order not to block the connection for too long
""",
FloatRange(0, unit='s'), default=0.05)
new_value = None
previous_value = None
expire = 0
is_float = True # assume float. will be fixed later
def isclose(self, v1, v2):
if v1 == v2:
return True
if self.is_float:
dt = self.datatype
try:
return math.isclose(v1, v2, abs_tol=dt.absolute_tolerance,
rel_tol=dt.relative_tolerance)
except AttributeError:
# fix once for ever when datatype is not a float
self.is_float = False
return False
def __set_name__(self, owner, name):
try:
rfunc = getattr(owner, f'read_{name}')
wfunc = getattr(owner, f'write_{name}')
except AttributeError:
raise ProgrammingError(f'FrozenParam: methods read_{name} and write_{name} must exist') from None
super().__set_name__(owner, name)
def read_wrapper(self, pname=name, rfunc=rfunc):
pobj = self.parameters[pname]
value = rfunc(self)
if pobj.new_value is None:
return value
if not pobj.isclose(value, pobj.new_value):
if value == pobj.previous_value:
if time.time() < pobj.expire:
return pobj.new_value
self.log.warning('%s readback did not change within %g sec',
pname, pobj.timeout)
else:
# value has changed, but is not matching new value
self.log.warning('%s readback changed from %r to %r but %r was given',
pname, pobj.previous_value, value, pobj.new_value)
# readback value has changed or returned value is roughly equal to the new value
pobj.new_value = None
return value
def write_wrapper(self, value, wfunc=wfunc, rfunc=rfunc, read_wrapper=read_wrapper, pname=name):
pobj = self.parameters[pname]
pobj.previous_value = rfunc(self)
pobj.new_value = wfunc(self, value)
if pobj.new_value is None: # as wfunc is the unwrapped write_* method, the return value may be None
pobj.new_value = value
pobj.expire = time.time() + pobj.timeout
for cnt in range(pobj.n_polls):
if cnt: # we may be lucky, and the readback value has already changed
time.sleep(pobj.interval)
value = read_wrapper(self)
if pobj.new_value is None:
return value
return pobj.new_value
setattr(owner, f'read_{name}', read_wrapper)
setattr(owner, f'write_{name}', write_wrapper)

View File

@ -1,104 +0,0 @@
# Author: Wouter Gruenewald<wouter.gruenewald@psi.ch>
from frappy.core import StringType, BoolType, EnumType, FloatRange, Parameter, Property, PersistentParam, Command, IDLE, ERROR, WARN, BUSY, Drivable
class PeristalticPump(Drivable):
value = Parameter('Pump speed', FloatRange(0,100,unit="%"), default=0)
target = Parameter('Target pump speed', FloatRange(0,100,unit="%"), default=0)
status = Parameter()
addr_AO = Property('Address of the analog out', StringType())
addr_dir_relay = Property('Address of the direction relay', StringType())
addr_run_relay = Property('Address of the running relay', StringType())
direction = Parameter('pump direction', EnumType('direction', CLOCKWISE=0, ANTICLOCKWISE=1), default=0, readonly=False)
active = Parameter('pump running', BoolType(), default=False, readonly=False)
def initModule(self):
super().initModule()
with open('/sys/class/ionopimax/analog_out/'+self.addr_AO+'_enabled', 'w') as f :
f.write('0')
with open('/sys/class/ionopimax/analog_out/'+self.addr_AO+'_mode', 'w') as f :
f.write('V')
with open('/sys/class/ionopimax/analog_out/'+self.addr_AO, 'w') as f :
f.write('0')
with open('/sys/class/ionopimax/analog_out/'+self.addr_AO+'_enabled', 'w') as f :
f.write('1')
def shutdownModule(self):
'''Disable analog output'''
with open('/sys/class/ionopimax/analog_out/'+self.addr_AO, 'w') as f :
f.write('0')
with open('/sys/class/ionopimax/analog_out/'+self.addr_AO+'_enabled', 'w') as f :
f.write('0')
def read_value(self):
with open('/sys/class/ionopimax/analog_out/'+self.addr_AO, 'r') as f :
raw_value = f.read().strip('\n')
value = (int(raw_value) / 5000) * 100
return value
def write_target(self, target):
raw_value = (target / 100)*5000
with open('/sys/class/ionopimax/analog_out/'+self.addr_AO, 'w') as f :
f.write(str(int(raw_value)))
return target
def read_direction(self):
with open('/sys/class/ionopimax/digital_out/'+self.addr_dir_relay, 'r') as f :
raw_direction = f.read().strip('\n')
if raw_direction == '0' or raw_direction == 'F':
return 0
if raw_direction == '1' or raw_direction == 'S':
return 1
else:
return None
def write_direction(self, direction):
if direction == 0:
raw_direction = '0'
elif direction == 1:
raw_direction = '1'
with open('/sys/class/ionopimax/digital_out/'+self.addr_dir_relay, 'w') as f :
f.write(raw_direction)
return direction
def read_active(self):
with open('/sys/class/ionopimax/digital_out/'+self.addr_run_relay, 'r') as f :
raw_active = f.read().strip('\n')
if raw_active == '0' or raw_active == 'F':
return False
elif raw_active == '1' or raw_active == 'S':
return True
else:
return None
def write_active(self, active):
if active == False:
raw_active = '0'
elif active == True:
raw_active = '1'
with open('/sys/class/ionopimax/digital_out/'+self.addr_run_relay, 'w') as f :
f.write(raw_active)
return active
def read_status(self):
with open('/sys/class/ionopimax/digital_out/'+self.addr_dir_relay, 'r') as f :
raw_direction = f.read().strip('\n')
with open('/sys/class/ionopimax/digital_out/'+self.addr_run_relay, 'r') as f :
raw_active = f.read().strip('\n')
if raw_direction == 'F' or raw_direction == 'S':
return ERROR, 'Fault on direction relay'
elif raw_active == 'F' or raw_active == 'S':
return ERROR, 'Fault on pump activation relay'
elif self.active == True:
return BUSY, 'Pump running'
else:
return IDLE, ''
@Command
def stop(self):
self.write_active(False)

View File

@ -15,6 +15,8 @@
#
# Module authors: Oksana Shliakhtun <oksana.shliakhtun@psi.ch>
# *****************************************************************************
"""Thermo Haake Phoenix P1 Bath Circulator"""
import re
import time
from frappy.core import StringIO, HasIO, Parameter, FloatRange, BoolType, \
@ -22,7 +24,13 @@ from frappy.core import StringIO, HasIO, Parameter, FloatRange, BoolType, \
from frappy_psi.convergence import HasConvergence
from frappy.errors import CommunicationFailedError
def convert(string):
"""
Converts reply to a number
:param string: reply from the command
:return: number
"""
number = re.sub(r'[^0-9.-]', '', string)
return float(number)
@ -55,11 +63,21 @@ class TemperatureLoop(HasIO, HasConvergence, Drivable):
]
def get_values_status(self):
"""
Supplementary command for the operating status method.
Removes the extra symbol and converts each status value into integer.
:return: array of integers
"""
reply = self.communicate('B')
string = reply.rstrip('$')
return [int(val) for val in string]
def read_status(self): # control_active update
"""
Operating status.
:return: statu type and message
"""
values_str = self.get_values_status()
self.read_control_active()
@ -72,6 +90,10 @@ class TemperatureLoop(HasIO, HasConvergence, Drivable):
return IDLE, ''
def read_value(self):
"""
F1 - internal temperature, F2 - external temperature
:return: float temperature value
"""
if self.mode == 1:
value = self.communicate('F1')
else:
@ -79,6 +101,11 @@ class TemperatureLoop(HasIO, HasConvergence, Drivable):
return convert(value)
def write_control_active(self, value):
"""
Turning on/off the heating, pump and regulation
:param value: 0 is OFF, 1 is ON
:return:
"""
if value is True:
self.communicate('GO') # heating and pump run
self.communicate('W SR') # regulation
@ -99,6 +126,11 @@ class TemperatureLoop(HasIO, HasConvergence, Drivable):
return convert(string)
def write_target(self, target):
"""
Selecting Celsius, setting the target
:param target: target
:return: target
"""
self.write_control_active(True)
self.read_status()
self.communicate('W TE C')
@ -106,6 +138,11 @@ class TemperatureLoop(HasIO, HasConvergence, Drivable):
return target
def write_mode(self, mode):
"""
Switching to internal or external control
:param mode: internal/external
:return: selected mode
"""
if mode == 1:
self.communicate('W IN')
self.communicate('W EX')
@ -113,7 +150,7 @@ class TemperatureLoop(HasIO, HasConvergence, Drivable):
@Command
def clear_errors(self):
""" Reset after error"""
""" Reset after error. Otherwise the status will not be updated"""
if self.read_status()[0] == ERROR:
try:
self.communicate('ER')

View File

@ -231,17 +231,14 @@ class ResChannel(Channel):
def _read_value(self):
"""read value, without update"""
now = time.monotonic()
if now - 0.5 < max(self._last_range_change, self.switcher._start_switch) + self.pause:
if now + 0.5 < max(self._last_range_change, self.switcher._start_switch) + self.pause:
return None
result = float(self.communicate('RDGR?%d' % self.channel))
if result == 0:
if self.autorange:
rng = int(max(self.minrange, self.range)) # convert from enum to int
self.write_range(min(self.MAX_RNG, rng + 1))
return None
if self.autorange:
self.fix_autorange()
if now - 0.5 > self._last_range_change + self.pause:
if now + 0.5 > self._last_range_change + self.pause:
rng = int(max(self.minrange, self.range)) # convert from enum to int
if self.status[0] < self.Status.ERROR:
if abs(result) > self.RES_SCALE[rng]:
@ -254,10 +251,8 @@ class ResChannel(Channel):
lim -= 0.05 # not more than 4 steps at once
# effectively: <0.16 %: 4 steps, <1%: 3 steps, <5%: 2 steps, <20%: 1 step
elif rng < self.MAX_RNG:
self.log.debug('increase range due to error %d', rng)
rng = min(self.MAX_RNG, rng + 1)
if rng != self.range:
self.log.debug('range change to %d', rng)
self.write_range(rng)
self._last_range_change = now
return result
@ -386,10 +381,6 @@ class TemperatureLoop(HasConvergence, TemperatureChannel, Drivable):
htrrng = Parameter('', EnumType(HTRRNG), readonly=False)
_control_active = False
def doPoll(self):
super().doPoll()
self.set_htrrng()
@Command
def control_off(self):
"""switch control off"""

View File

@ -198,10 +198,6 @@ class MotorValve(PersistentMixin, Drivable):
@Command
def stop(self):
"""stop at current position
state will probably be undefined
"""
self._state.stop()
self.motor.stop()

View File

@ -22,7 +22,7 @@
"""modules to access parameters"""
from frappy.core import Drivable, EnumType, IDLE, Attached, StringType, Property, \
Parameter, BoolType, FloatRange, Readable, ERROR, nopoll
Parameter, FloatRange, Readable, ERROR
from frappy.errors import ConfigError
from frappy_psi.convergence import HasConvergence
from frappy_psi.mixins import HasRamp
@ -72,21 +72,19 @@ class Driv(Drivable):
raise ConfigError('illegal recursive read/write module')
super().checkProperties()
def registerUpdates(self):
self.read.addCallback(self.read_param, self.announceUpdate, 'value')
self.write.addCallback(self.write_param, self.announceUpdate, 'target')
#def registerUpdates(self):
# self.read.valueCallbacks[self.read_param].append(self.update_value)
# self.write.valueCallbacks[self.write_param].append(self.update_target)
#
#def startModule(self, start_events):
# start_events.queue(self.registerUpdates)
# super().startModule(start_events)
def startModule(self, start_events):
start_events.queue(self.registerUpdates)
super().startModule(start_events)
@nopoll
def read_value(self):
return getattr(self.read, f'read_{self.read_param}')()
return getattr(self.read, f'{self.read_param}')
@nopoll
def read_target(self):
return getattr(self.write, f'read_{self.write_param}')()
return getattr(self.write, f'{self.write_param}')
def read_status(self):
return IDLE, ''
@ -132,7 +130,7 @@ def set_enabled(modobj, value):
def get_value(obj, default):
"""get the value of given module. if not valid, return the default"""
"""get the value of given module. if not valid, return the limit (min_high or max_low)"""
if not getattr(obj, 'enabled', True):
return default
# consider also that a value 0 is invalid
@ -150,61 +148,51 @@ class SwitchDriv(HasConvergence, Drivable):
max_low = Parameter('maximum low target', FloatRange(unit='$'), readonly=False)
# disable_other = Parameter('whether to disable unused channel', BoolType(), readonly=False)
selected = Parameter('selected module', EnumType(low=LOW, high=HIGH), readonly=False, default=0)
autoswitch = Parameter('switch sensor automatically', BoolType(), readonly=False, default=True)
_switch_target = None # if not None, switch to selection when mid range is reached
_switch_target = None # if not None, switch to selection mhen mid range is reached
# TODO: copy units from attached module
# TODO: callbacks for updates
def doPoll(self):
super().doPoll()
if self._switch_target is not None:
mid = (self.min_high + self.max_low) * 0.5
if self._switch_target == HIGH:
low = get_value(self.low, mid) # returns mid when low is invalid
if low > mid:
self.value = self.low.value
self._switch_target = None
self.write_target(self.target)
return
else:
high = get_value(self.high, mid) # return mid when high is invalid
if high < mid: # change to self.max_low
self.value = self.high.value
self._switch_target = None
self.write_target(self.target)
return
if not self.isBusy() and self.autoswitch:
if self.isBusy():
if self._switch_target is not None:
mid = (self.min_high + self.max_low) * 0.5
if self._switch_target == HIGH:
low = get_value(self.low, mid) # returns mid when low is invalid
if low > mid:
self.value = self.low.value
self._switch_target = None
self.write_target(self.target)
return
else:
high = get_value(self.high, mid) # return mid then high is invalid
if high < mid:
self.value = self.high.value
self._switch_target = None
self.write_target(self.target)
return
else:
low = get_value(self.low, self.max_low)
high = get_value(self.high, self.min_high)
low_valid = low < self.max_low
high_valid = high > self.min_high
if high_valid and high > self.max_low:
if not low_valid and not self.low.control_active:
if not low_valid:
set_enabled(self.low, False)
return
if low_valid and low < self.min_high:
if not high_valid and not self.high.control_active:
if not high_valid:
set_enabled(self.high, False)
return
# keep only one channel on
#set_enabled(self.low, True)
#set_enabled(self.high, True)
def get_selected(self):
low = get_value(self.low, self.max_low)
high = get_value(self.high, self.min_high)
if low < self.min_high:
return 0
if high > self.max_low:
return 1
return self.selected
set_enabled(self.low, True)
set_enabled(self.high, True)
def read_value(self):
return self.low.value if self.get_selected() == LOW else self.high.value
return self.low.value if self.selected == LOW else self.high.value
def read_status(self):
status = self.low.status if self.get_selected() == LOW else self.high.status
status = self.low.status if self.selected == LOW else self.high.status
if status[0] >= ERROR:
return status
return super().read_status() # convergence status
@ -214,23 +202,22 @@ class SwitchDriv(HasConvergence, Drivable):
selected = self.selected
target1 = target
self._switch_target = None
if target > self.max_low * 0.75 + self.min_high * 0.25:
if target > self.max_low:
if self.value < self.min_high:
target1 = min(target, self.max_low)
target1 = self.max_low
self._switch_target = HIGH
selected = LOW
else:
this, other = other, this
selected = HIGH
elif target < self.min_high * 0.75 + self.max_low * 0.25:
# reinstall this with higher threshold (e.g. 4 K)?
#if self.value > self.max_low:
# target1 = max(self.min_high, target)
# self._switch_target = LOW
# this, other = other, this
# selected = HIGH
#else:
selected = LOW
elif target < self.min_high:
if self.value > self.max_low:
target1 = self.min_high
self._switch_target = LOW
this, other = other, this
selected = HIGH
else:
selected = LOW
elif self.selected == HIGH:
this, other = other, this
if hasattr(other, 'control_off'):

View File

@ -62,7 +62,7 @@ class Motor(HasOffset, HasStates, PersistentMixin, HasIO, Drivable):
encoder_mode = Parameter('how to treat the encoder', EnumType('encoder', NO=0, READ=1, CHECK=2),
default=1, readonly=False)
check_limit_switches = Parameter('whether limit switches are checked',BoolType(),
check_limit_switches = Parameter('whethter limit switches are checked',BoolType(),
default=0, readonly=False)
value = PersistentParam('angle', FloatRange(unit='deg'))
status = PersistentParam()
@ -90,8 +90,6 @@ class Motor(HasOffset, HasStates, PersistentMixin, HasIO, Drivable):
status_bits = ['power stage error', 'undervoltage', 'overtemperature', 'active',
'lower switch active', 'upper switch active', 'step failure', 'encoder error']
_doing_reference = False
def get(self, cmd):
return self.communicate(f'{self.address:x}{self.axis}{cmd}')
@ -180,14 +178,10 @@ class Motor(HasOffset, HasStates, PersistentMixin, HasIO, Drivable):
def doPoll(self):
super().doPoll()
if self._running and not self.isBusy() and not self._doing_reference:
if self._running and not self.isBusy():
if time.time() > self._stopped_at + 5:
self.log.warning('stop motor not started by us')
self.hw_stop()
if self._doing_reference and self.get('=H') == 'E' :
self.status = IDLE, ''
self.target = 0
self._doing_reference = False
def read_status(self):
hexstatus = 0x100
@ -213,9 +207,6 @@ class Motor(HasOffset, HasStates, PersistentMixin, HasIO, Drivable):
if status[0] == ERROR:
self._blocking_error = status[1]
return status
if self._doing_reference and self.get('=H') == 'N':
status = BUSY, 'Doing reference run'
return status
return super().read_status() # status from state machine
def check_moving(self):
@ -355,10 +346,3 @@ class Motor(HasOffset, HasStates, PersistentMixin, HasIO, Drivable):
self.status = 'IDLE', 'after error reset'
self._blocking_error = None
self.target = self.value # clear error in target
@Command
def make_ref_run(self):
'''Do reference run'''
self._doing_reference = True
self.status = BUSY, 'Doing reference run'
self.communicate(f'{self.address:x}{self.axis}0-')

View File

@ -483,10 +483,6 @@ class Temp(PpmsDrivable):
self._expected_target_time = time.time() + abs(target - self.value) * 60.0 / max(0.1, ramp)
def stop(self):
"""set setpoint to current value
but restrict to values between last target and current target
"""
if not self.isDriving():
return
if self.status[0] != StatusType.STABILIZING:
@ -616,7 +612,6 @@ class Field(PpmsDrivable):
# do not execute FIELD command, as this would trigger a ramp up of leads current
def stop(self):
"""stop at current driven Field"""
if not self.isDriving():
return
newtarget = clamp(self._last_target, self.value, self.target)
@ -719,7 +714,6 @@ class Position(PpmsDrivable):
return value # do not execute MOVE command, as this would trigger an unnecessary move
def stop(self):
"""stop motor"""
if not self.isDriving():
return
newtarget = clamp(self._last_target, self.value, self.target)

74
frappy_psi/pulse.py Normal file
View File

@ -0,0 +1,74 @@
# *****************************************************************************
# 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: Oksana Shliakhtun <oksana.shliakhtun@psi.ch>
# *****************************************************************************
"""Keithley Instruments 2601B-PULSE System (not finished)"""
from frappy.core import StringIO, HasIO, Readable, Writable, \
Parameter, FloatRange, EnumType
class PulseIO(StringIO):
end_of_line = '\n'
identification = [('*IDN?', 'Keithley Instruments, Model 2601B-PULSE,.*')]
class Base(HasIO):
def set_source(self):
"""
Set the source -always current
:return:
"""
return self.communicate(f'smua.source.func = smua.OUTPUT_DCAMPS')
def get_par(self, cmd):
return self.communicate(f'reading = smua.measure.{cmd}()')
def auto_onof(self, val):
if val == 1:
return f'smua.AUTORANGE_ON'
if val == 0:
return f'smua.AUTORANGE_OFF'
def set_measure(self, cmd, val):
return self.communicate(f'smua.measure.{cmd} = {val}')
class Create_Pulse(Base, Readable, Writable):
target = Parameter('source target', FloatRange, unit='A', readonly=False)
width = Parameter('pulse width', FloatRange, unit="s", readonly=False)
resistance = Parameter('resistance', FloatRange)
SOURCE_RANGE = ['100nA', '1uA', '10uA', '100uA', '1mA', '10mA', '100mA', '1A', '3A']
range = Parameter('source range', EnumType('source range',
{name: idx for idx, name in enumerate(SOURCE_RANGE)}), readonly=False)
def read_range(self):
return self.range
def write_range(self, range):
self.communicate(f'smua.source.rangei = {range}')
return self.range
def write_target(self, target):
return self.communicate(f'smua.source.leveli = {target}')
def read_resistance(self):
return self.communicate('reading = smua.measure.r()')
class Script(Create_Pulse):

View File

@ -16,7 +16,7 @@
# Module authors:
# Oksana Shliakhtun <oksana.shliakhtun@psi.ch>
# *****************************************************************************
"""Temperature Controller TC1 Quantum NorthWest"""
from frappy.core import Readable, Parameter, FloatRange, IDLE, ERROR, BoolType,\
StringIO, HasIO, Property, WARN, Drivable, BUSY, StringType, Done
@ -31,10 +31,17 @@ class QnwIO(StringIO):
class SensorTC1(HasIO, Readable):
ioClass = QnwIO
value = Parameter(unit='degC', min=-15, max=120)
value = Parameter(unit='degC', min=-55, max=150)
channel = Property('channel name', StringType())
def set_param(self, adr, value=None):
"""
Set parameter.
Every command starts with "[F1", and the end of line is "]".
:param adr: second part of the command
:param value: value to set
:return: value converted to float
"""
short = adr.split()[0]
# try 3 times in case we got an asynchronous message
for _ in range(3):
@ -66,7 +73,7 @@ class SensorTC1(HasIO, Readable):
class TemperatureLoopTC1(SensorTC1, Drivable):
value = Parameter('temperature', unit='degC')
target = Parameter('setpoint', unit='degC', min=-5, max=110)
target = Parameter('setpoint', unit='degC', min=-55, max=150)
control = Parameter('temperature control flag', BoolType(), readonly=False)
ramp = Parameter('ramping value', FloatRange, unit='degC/min', readonly=False)
ramp_used = Parameter('ramping status', BoolType(), default=False, readonly=False)
@ -80,6 +87,16 @@ class TemperatureLoopTC1(SensorTC1, Drivable):
return self.get_param('MT')
def read_status(self):
"""
the device returns 4 symbols according to the current status. These symbols are:
”0” or “1” - number of unreported errors
”+” or “-” - stirrer is on/off
”+” or ”-” - temperature control is on/off
”S” or “C” - current sample holder tempeerature is stable/changing
There could be the fifth status symbol:
”+” or “-” or “W” - rampping is on/off/waiting
:return: status messages
"""
status = super().read_status()
if status[0] == ERROR:
return status
@ -141,6 +158,5 @@ class TemperatureLoopTC1(SensorTC1, Drivable):
return value
def stop(self):
"""stop at current value (does nothing if ramp is not used)"""
if self.control and self.ramp_used:
self.write_target(self.value)

View File

@ -1,70 +0,0 @@
from frappy.core import StringType, BoolType, Parameter, Property, PersistentParam, Command, IDLE, ERROR, WARN, Writable
import time
class RheoTrigger(Writable):
addr = Property('Port address', StringType())
value = Parameter('Output state', BoolType(), default=0)
target = Parameter('target', BoolType(), default=0, readonly=False)
status = Parameter()
doBeep = Property('Make noise', BoolType(), default=0)
_status = 0
def initModule(self):
super().initModule()
with open('/sys/class/ionopimax/digital_io/'+self.addr+'_mode', 'w') as f :
f.write('out')
if self.doBeep:
with open('/sys/class/ionopimax/buzzer/beep', 'w') as f :
f.write('200 50 3')
def read_value(self):
with open('/sys/class/ionopimax/digital_io/'+self.addr, 'r') as f :
file_value = f.read()
if file_value == '0\n':
value = False
self._status = 0
elif file_value == '1\n':
value = True
self._status = 1
else:
self._status = -1
value = False
return value
def write_target(self,target):
if target == self.value:
return target
else:
with open('/sys/class/ionopimax/digital_io/'+self.addr, 'w') as f :
if target == True:
f.write('1')
elif target == False:
f.write('0')
time.sleep(0.05)
if self.doBeep:
with open('/sys/class/ionopimax/buzzer/beep', 'w') as f :
f.write('200')
self.status = self.read_status()
return target
def read_status(self):
self.value = self.read_value()
if self._status == 0:
return IDLE, 'Signal low'
elif self._status == 1:
return IDLE, 'Signal high'
else:
return ERROR, 'Cannot read status'
@Command
def toggle(self):
"""Toggle output"""
value = self.read_value()
if value == True:
self.write_target(False)
else:
self.write_target(True)

View File

@ -39,13 +39,12 @@ from os.path import expanduser, join, exists
from frappy.client import ProxyClient
from frappy.datatypes import ArrayOf, BoolType, \
EnumType, FloatRange, IntRange, StringType
from frappy.core import IDLE, BUSY, ERROR
from frappy.errors import ConfigError, HardwareError, CommunicationFailedError
from frappy.errors import ConfigError, HardwareError, secop_error, CommunicationFailedError
from frappy.lib import generalConfig, mkthread
from frappy.lib.asynconn import AsynConn, ConnectionClosed
from frappy.modulebase import Done
from frappy.modules import Attached, Command, Drivable, \
from frappy.modules import Attached, Command, Done, Drivable, \
Module, Parameter, Property, Readable, Writable
from frappy.protocol.dispatcher import make_update
CFG_HEADER = """Node('%(config)s.sea.psi.ch',
@ -108,6 +107,7 @@ class SeaClient(ProxyClient, Module):
service = Property("main/stick/addons", StringType(), default='')
visibility = 'expert'
default_json_file = {}
_connect_thread = None
_instance = None
_last_connect = 0
@ -124,8 +124,6 @@ class SeaClient(ProxyClient, Module):
self.shutdown = False
self.path2param = {}
self._write_lock = threading.Lock()
self._connect_thread = None
self._connected = False
config = opts.get('config')
if isinstance(config, dict):
config = config['value']
@ -137,11 +135,14 @@ class SeaClient(ProxyClient, Module):
Module.__init__(self, name, log, opts, srv)
def doPoll(self):
if not self._connected and time.time() > self._last_connect + 10:
if not self._last_connect:
self.log.info('reconnect to SEA %s', self.service)
if self._connect_thread is None:
self._connect_thread = mkthread(self._connect)
if not self.asynio and time.time() > self._last_connect + 10:
with self._write_lock:
# make sure no more connect thread is running
if self._connect_thread and self._connect_thread.isAlive():
return
if not self._last_connect:
self.log.info('reconnect to SEA %s', self.service)
self._connect_thread = mkthread(self._connect, None)
def register_obj(self, module, obj):
self.objects.add(obj)
@ -149,105 +150,99 @@ class SeaClient(ProxyClient, Module):
self.path2param.setdefault(k, []).extend(v)
self.register_callback(module.name, module.updateEvent)
def _connect(self):
try:
if self.syncio:
def _connect(self, started_callback):
self.asynio = None
if self.syncio:
# trigger syncio reconnect in self.request()
try:
self.syncio.disconnect()
except Exception:
pass
self.syncio = None
self._last_connect = time.time()
if self._instance:
try:
from servicemanager import SeaManager # pylint: disable=import-outside-toplevel
SeaManager().do_start(self._instance)
except ImportError:
pass
if '//' not in self.uri:
self.uri = 'tcp://' + self.uri
self.asynio = AsynConn(self.uri)
reply = self.asynio.readline()
if reply != b'OK':
raise CommunicationFailedError('reply %r should be "OK"' % reply)
for _ in range(2):
self.asynio.writeline(b'Spy 007')
reply = self.asynio.readline()
if reply == b'Login OK':
break
else:
raise CommunicationFailedError('reply %r should be "Login OK"' % reply)
result = self.request('frappy_config %s %s' % (self.service, self.config))
if result.startswith('ERROR:'):
raise CommunicationFailedError(f'reply from frappy_config: {result}')
# frappy_async_client switches to the json protocol (better for updates)
self.asynio.writeline(b'frappy_async_client')
self.asynio.writeline(('get_all_param ' + ' '.join(self.objects)).encode())
self._connect_thread = None
mkthread(self._rxthread, started_callback)
def request(self, command, quiet=False):
"""send a request and wait for reply"""
with self._write_lock:
if not self.syncio or not self.syncio.connection:
if not self.asynio or not self.asynio.connection:
try:
self._connect_thread.join()
except AttributeError:
pass
# let doPoll do the reconnect
self.pollInfo.trigger(True)
raise ConnectionClosed('disconnected - reconnect later')
self.syncio = AsynConn(self.uri)
assert self.syncio.readline() == b'OK'
self.syncio.writeline(b'seauser seaser')
assert self.syncio.readline() == b'Login OK'
self.log.info('connected to %s', self.uri)
try:
self.syncio.flush_recv()
ft = 'fulltransAct' if quiet else 'fulltransact'
self.syncio.writeline(('%s %s' % (ft, command)).encode())
result = None
deadline = time.time() + 10
while time.time() < deadline:
reply = self.syncio.readline()
if reply is None:
continue
reply = reply.decode()
if reply.startswith('TRANSACTIONSTART'):
result = []
continue
if reply == 'TRANSACTIONFINISHED':
if result is None:
self.log.info('missing TRANSACTIONSTART on: %s', command)
return ''
if not result:
return ''
return '\n'.join(result)
if result is None:
self.log.info('swallow: %s', reply)
continue
if not result:
result = [reply.split('=', 1)[-1]]
else:
result.append(reply)
except ConnectionClosed:
try:
self.syncio.disconnect()
except Exception:
pass
self._last_connect = time.time()
if self._instance:
try:
from servicemanager import SeaManager # pylint: disable=import-outside-toplevel
SeaManager().do_start(self._instance)
except ImportError:
pass
if '//' not in self.uri:
self.uri = 'tcp://' + self.uri
self.asynio = AsynConn(self.uri)
reply = self.asynio.readline()
if reply != b'OK':
raise CommunicationFailedError('reply %r should be "OK"' % reply)
for _ in range(2):
self.asynio.writeline(b'Spy 007')
reply = self.asynio.readline()
if reply == b'Login OK':
break
else:
raise CommunicationFailedError('reply %r should be "Login OK"' % reply)
self.syncio = AsynConn(self.uri)
assert self.syncio.readline() == b'OK'
self.syncio.writeline(b'seauser seaser')
assert self.syncio.readline() == b'Login OK'
self.log.info('connected to %s', self.uri)
self.syncio = None
raise
raise TimeoutError('no response within 10s')
result = self.raw_request('frappy_config %s %s' % (self.service, self.config))
if result.startswith('ERROR:'):
raise CommunicationFailedError(f'reply from frappy_config: {result}')
# frappy_async_client switches to the json protocol (better for updates)
self.asynio.writeline(b'frappy_async_client')
self.asynio.writeline(('get_all_param ' + ' '.join(self.objects)).encode())
self._connected = True
mkthread(self._rxthread)
finally:
self._connect_thread = None
def request(self, command, quiet=False):
with self._write_lock:
if not self._connected:
if self._connect_thread is None:
# let doPoll do the reconnect
self.pollInfo.trigger(True)
raise ConnectionClosed('disconnected - reconnect is tried later')
return self.raw_request(command, quiet)
def raw_request(self, command, quiet=False):
"""send a request and wait for reply"""
try:
self.syncio.flush_recv()
ft = 'fulltransAct' if quiet else 'fulltransact'
self.syncio.writeline(('%s %s' % (ft, command)).encode())
result = None
deadline = time.time() + 10
while time.time() < deadline:
reply = self.syncio.readline()
if reply is None:
continue
reply = reply.decode()
if reply.startswith('TRANSACTIONSTART'):
result = []
continue
if reply == 'TRANSACTIONFINISHED':
if result is None:
self.log.info('missing TRANSACTIONSTART on: %s', command)
return ''
if not result:
return ''
return '\n'.join(result)
if result is None:
self.log.info('swallow: %s', reply)
continue
if not result:
result = [reply.split('=', 1)[-1]]
else:
result.append(reply)
raise TimeoutError('no response within 10s')
except ConnectionClosed:
self.close_connections()
raise
def close_connections(self):
connections = self.syncio, self.asynio
self._connected = False
self.syncio = self.asynio = None
for conn in connections:
try:
conn.disconnect()
except Exception:
pass
def _rxthread(self):
def _rxthread(self, started_callback):
recheck = None
while not self.shutdown:
if recheck and time.time() > recheck:
@ -263,7 +258,11 @@ class SeaClient(ProxyClient, Module):
if reply is None:
continue
except ConnectionClosed:
self.close_connections()
try:
self.asynio.disconnect()
except Exception:
pass
self.asynio = None
break
try:
msg = json.loads(reply)
@ -290,6 +289,9 @@ class SeaClient(ProxyClient, Module):
data = msg['data']
if flag == 'finish' and obj == 'get_all_param':
# first updates have finished
if started_callback:
started_callback()
started_callback = None
continue
if flag != 'hdbevent':
if obj not in ('frappy_async_client', 'get_all_param'):
@ -350,7 +352,7 @@ class SeaClient(ProxyClient, Module):
class SeaConfigCreator(SeaClient):
def startModule(self, start_events):
"""save objects (and sub-objects) description and exit"""
self._connect()
self._connect(None)
reply = self.request('describe_all')
reply = ''.join('' if line.startswith('WARNING') else line for line in reply.split('\n'))
description, reply = json.loads(reply)
@ -642,7 +644,22 @@ class SeaModule(Module):
if upd:
upd(value, timestamp, readerror)
return
self.announceUpdate(parameter, value, readerror, timestamp)
try:
pobj = self.parameters[parameter]
except KeyError:
self.log.error('do not know %s:%s', self.name, parameter)
raise
pobj.timestamp = timestamp
# should be done here: deal with clock differences
if not readerror:
try:
pobj.value = value # store the value even in case of a validation error
pobj.value = pobj.datatype(value)
except Exception as e:
readerror = secop_error(e)
pobj.readerror = readerror
if pobj.export:
self.secNode.srv.dispatcher.broadcast_event(make_update(self.name, pobj))
def initModule(self):
self.io.register_obj(self, self.sea_object)
@ -653,35 +670,20 @@ class SeaModule(Module):
class SeaReadable(SeaModule, Readable):
_readerror = None
_status = IDLE, ''
def update_value(self, value, timestamp, readerror):
# make sure status is always ERROR when reading value fails
self._readerror = readerror
if readerror:
self.read_status() # forced ERROR status
self.announceUpdate('value', value, readerror, timestamp)
else: # order is important
self.value = value # includes announceUpdate
self.read_status() # send event for ordinary self._status
def update_status(self, value, timestamp, readerror):
if readerror:
value = f'{readerror.name} - {readerror}'
value = repr(readerror)
if value == '':
self._status = IDLE, ''
self.status = (self.Status.IDLE, '')
else:
self._status = ERROR, value
self.read_status()
self.status = (self.Status.ERROR, value)
def read_status(self):
if self._readerror:
return ERROR, f'{self._readerror.name} - {self._readerror}'
return self._status
return self.status
class SeaWritable(SeaReadable, Writable):
class SeaWritable(SeaModule, Writable):
def read_value(self):
return self.target
@ -691,13 +693,20 @@ class SeaWritable(SeaReadable, Writable):
self.value = value
class SeaDrivable(SeaReadable, Drivable):
class SeaDrivable(SeaModule, Drivable):
_sea_status = ''
_is_running = 0
def earlyInit(self):
super().earlyInit()
self._run_event = threading.Event()
def read_status(self):
return self.status
# def read_target(self):
# return self.target
def write_target(self, value):
self._run_event.clear()
self.io.query(f'run {self.sea_object} {value}')
@ -705,20 +714,25 @@ class SeaDrivable(SeaReadable, Drivable):
self.log.warn('target changed but is_running stays 0')
return value
def update_status(self, value, timestamp, readerror):
if not readerror:
self._sea_status = value
self.updateStatus()
def update_is_running(self, value, timestamp, readerror):
if not readerror:
self._is_running = value
self.read_status()
self.updateStatus()
if value:
self._run_event.set()
def read_status(self):
status = super().read_status()
if self._is_running:
if status[0] >= ERROR:
return ERROR, 'BUSY + ' + status[1]
return BUSY, 'driving'
return status
def updateStatus(self):
if self._sea_status:
self.status = (self.Status.ERROR, self._sea_status)
elif self._is_running:
self.status = (self.Status.BUSY, 'driving')
else:
self.status = (self.Status.IDLE, '')
def updateTarget(self, module, parameter, value, timestamp, readerror):
if value is not None:

View File

@ -27,7 +27,7 @@ import numpy as np
from scipy.interpolate import splev, splrep # pylint: disable=import-error
from frappy.core import Attached, BoolType, Parameter, Readable, StringType, \
FloatRange, nopoll
FloatRange
def linear(x):
@ -195,40 +195,35 @@ class Sensor(Readable):
if self.description == '_':
self.description = f'{self.rawsensor!r} calibrated with curve {self.calib!r}'
def doPoll(self):
self.read_status()
def write_calib(self, value):
self._calib = CalCurve(value)
return value
def _get_value(self, rawvalue):
def update_value(self, value):
if self.abs:
rawvalue = abs(float(rawvalue))
return self._calib(rawvalue)
value = abs(float(value))
self.value = self._calib(value)
self._value_error = None
def _get_status(self, rawstatus):
return rawstatus if self._value_error is None else (self.Status.ERROR, self._value_error)
def error_update_value(self, err):
if self.abs and str(err) == 'R_UNDER': # hack: ignore R_UNDER from ls370
self._value_error = None
return None
self._value_error = repr(err)
raise err
def update_value(self, rawvalue, err=None):
if err:
if self.abs and str(err) == 'R_UNDER': # hack: ignore R_UNDER from ls370
self._value_error = None
return
err = repr(err)
def update_status(self, value):
if self._value_error is None:
self.status = value
else:
try:
self.value = self._get_value(rawvalue)
except Exception as e:
err = repr(e)
if err != self._value_error:
self._value_error = err
self.status = self._get_status(self.rawsensor.status)
self.status = self.Status.ERROR, self._value_error
def update_status(self, rawstatus):
self.status = self._get_status(rawstatus)
@nopoll
def read_value(self):
return self._get_value(self.rawsensor.read_value())
return self._calib(self.rawsensor.read_value())
@nopoll
def read_status(self):
return self._get_status(self.rawsensor.read_status())
self.update_status(self.rawsensor.status)
return self.status

View File

@ -26,7 +26,6 @@ from frappy.datatypes import EnumType, FloatRange, StringType
from frappy.lib.enum import Enum
from frappy_psi.mercury import MercuryChannel, Mapped, off_on, HasInput
from frappy_psi import mercury
from frappy_psi.frozenparam import FrozenParam
actions = Enum(none=0, condense=1, circulate=2, collect=3)
open_close = Mapped(CLOSE=0, OPEN=1)
@ -40,23 +39,22 @@ class Action(MercuryChannel, Writable):
mix_channel = Property('mix channel', StringType(), 'T5')
still_channel = Property('cool down channel', StringType(), 'T4')
value = Parameter('running action', EnumType(actions))
target = FrozenParam('action to do', EnumType(none=0, condense=1, collect=3), readonly=False)
target = Parameter('action to do', EnumType(none=0, condense=1, collect=3), readonly=False)
_target = 0
def read_value(self):
return self.query('SYS:DR:ACTN', actions_map)
# as target is a FrozenParam, value might be still lag behind target
# but will be updated when changed from an other source
read_target = read_value
def read_target(self):
return self._target
def write_target(self, value):
self._target = value
self.change('SYS:DR:CHAN:COOL', self.cooldown_channel, str)
self.change('SYS:DR:CHAN:STIL', self.still_channel, str)
self.change('SYS:DR:CHAN:MC', self.mix_channel, str)
self.change('DEV:T5:TEMP:MEAS:ENAB', 'ON', str)
self.change('SYS:DR:ACTN', value, actions_map)
return value
return self.change('SYS:DR:ACTN', value, actions_map)
# actions:
# NONE (no action)
@ -76,7 +74,7 @@ class Action(MercuryChannel, Writable):
class Valve(MercuryChannel, Drivable):
kind = 'VALV'
value = Parameter('valve state', EnumType(closed=0, opened=1))
target = FrozenParam('valve target', EnumType(close=0, open=1))
target = Parameter('valve target', EnumType(close=0, open=1))
_try_count = None
@ -110,10 +108,6 @@ class Valve(MercuryChannel, Drivable):
self.change('DEV::VALV:SIG:STATE', self.target, open_close)
return BUSY, 'waiting'
# as target is a FrozenParam, value might be still lag behind target
# but will be updated when changed from an other source
read_target = read_value
def write_target(self, value):
if value != self.read_value():
self._try_count = 0
@ -126,18 +120,13 @@ class Valve(MercuryChannel, Drivable):
class Pump(MercuryChannel, Writable):
kind = 'PUMP'
value = Parameter('pump state', EnumType(off=0, on=1))
target = FrozenParam('pump target', EnumType(off=0, on=1))
target = Parameter('pump target', EnumType(off=0, on=1))
def read_value(self):
return self.query('DEV::PUMP:SIG:STATE', off_on)
# as target is a FrozenParam, value might be still lag behind target
# but will be updated when changed from an other source
read_target = read_value
def write_target(self, value):
self.change('DEV::PUMP:SIG:STATE', value, off_on)
return value
return self.change('DEV::PUMP:SIG:STATE', value, off_on)
def read_status(self):
return IDLE, ''

View File

@ -411,7 +411,6 @@ class Uniax(PersistentMixin, Drivable):
@Command()
def stop(self):
"""stop motor and control"""
if self.motor.isBusy():
self.log.info('stop motor')
self.motor_stop()

View File

@ -1,155 +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:
# Markus Zolliker <markus.zolliker@psi.ch>
#
# *****************************************************************************
"""test parameter callbacks"""
from test.test_modules import LoggerStub, ServerStub
import pytest
from frappy.core import Module, Parameter, FloatRange
from frappy.errors import WrongTypeError
WRONG_TYPE = WrongTypeError()
class Mod(Module):
a = Parameter('', FloatRange())
b = Parameter('', FloatRange())
c = Parameter('', FloatRange())
def read_a(self):
raise WRONG_TYPE
def read_b(self):
raise WRONG_TYPE
def read_c(self):
raise WRONG_TYPE
class Dbl(Module):
a = Parameter('', FloatRange())
b = Parameter('', FloatRange())
c = Parameter('', FloatRange())
_error_a = None
_value_b = None
_error_c = None
def update_a(self, value, err=None):
# treat error updates
try:
self.a = value * 2
except TypeError: # value is None -> err
self.announceUpdate('a', None, err)
def update_b(self, value):
self._value_b = value
# error updates are ignored
self.b = value * 2
def make(cls):
logger = LoggerStub()
srv = ServerStub({})
return cls('mod1', logger, {'description': ''}, srv)
def test_simple_callback():
mod1 = make(Mod)
result = []
def cbfunc(arg1, arg2, value):
result[:] = arg1, arg2, value
mod1.addCallback('a', cbfunc, 'ARG1', 'arg2')
mod1.a = 1.5
assert result == ['ARG1', 'arg2', 1.5]
result.clear()
with pytest.raises(WrongTypeError):
mod1.read_a()
assert not result # callback function is NOT called
def test_combi_callback():
mod1 = make(Mod)
result = []
def cbfunc(arg1, arg2, value, err=None):
result[:] = arg1, arg2, value, err
mod1.addCallback('a', cbfunc, 'ARG1', 'arg2')
mod1.a = 1.5
assert result == ['ARG1', 'arg2', 1.5, None]
result.clear()
with pytest.raises(WrongTypeError):
mod1.read_a()
assert result[:3] == ['ARG1', 'arg2', None] # callback function called with value None
assert isinstance(result[3], WrongTypeError)
def test_autoupdate():
mod1 = make(Mod)
mod2 = make(Dbl)
mod1.registerCallbacks(mod2, autoupdate=['c'])
result = {}
def cbfunc(pname, *args):
result[pname] = args
for param in 'a', 'b', 'c':
mod2.addCallback(param, cbfunc, param)
# test update_a without error
mod1.a = 5
assert mod2.a == 10
assert result.pop('a') == (10,)
# test update_a with error
with pytest.raises(WrongTypeError):
mod1.read_a()
assert result.pop('a') == (None, WRONG_TYPE)
# test that update_b is ignored in case of error
mod1.b = 3
assert mod2.b == 6 # no error
assert result.pop('b') == (6,)
with pytest.raises(WrongTypeError):
mod1.read_b()
assert 'b' not in result
# test autoupdate
mod1.c = 3
assert mod2.c == 3
assert result['c'] == (3,)
with pytest.raises(WrongTypeError):
mod1.read_c()
assert result['c'] == (None, WRONG_TYPE)

View File

@ -18,14 +18,12 @@
# Markus Zolliker <markus.zolliker@psi.ch>
#
# *****************************************************************************
"""test frappy.extparams"""
"""test frappy.mixins.HasCtrlPars"""
from test.test_modules import LoggerStub, ServerStub
import pytest
from frappy.core import FloatRange, Module, Parameter
from frappy.extparams import StructParam, FloatEnumParam
from frappy.errors import ProgrammingError
from frappy.structparam import StructParam
def test_with_read_ctrlpars():
@ -132,76 +130,3 @@ def test_order_dependence1():
def test_order_dependence2():
test_with_read_ctrlpars()
test_without_read_ctrlpars()
def test_float_enum():
class Mod(Module):
vrange = FloatEnumParam('voltage range', [
(1, '50uV'), '200 µV', '1mV', ('5mV', 0.006), (9, 'max', 0.024)], 'V')
gain = FloatEnumParam('gain factor', ('1', '2', '4', '8'), idx_name='igain')
dist = FloatEnumParam('distance', ('1m', '1mm', '1µm'), unit='m')
_vrange_idx = None
def write_vrange_idx(self, value):
self._vrange_idx = value
logger = LoggerStub()
updates = {}
srv = ServerStub(updates)
m = Mod('m', logger, {'description': ''}, srv)
assert m.write_vrange_idx(1) == 1
assert m._vrange_idx == '50uV'
assert m._vrange_idx == 1
assert m.vrange == 5e-5
assert m.write_vrange_idx(2) == 2
assert m._vrange_idx == '200 µV'
assert m._vrange_idx == 2
assert m.vrange == 2e-4
assert m.write_vrange(6e-5) == 5e-5 # round to the next value
assert m._vrange_idx == '50uV'
assert m._vrange_idx == 1
assert m.write_vrange(20e-3) == 24e-3 # round to the next value
assert m._vrange_idx == 'max'
assert m._vrange_idx == 9
for idx in range(4):
value = 2 ** idx
updates.clear()
assert m.write_igain(idx) == idx
assert updates == {'m': {'igain': idx, 'gain': value}}
assert m.igain == idx
assert m.igain == str(value)
assert m.gain == value
for idx in range(4):
value = 2 ** idx
assert m.write_gain(value) == value
assert m.igain == idx
assert m.igain == str(value)
for idx in range(3):
value = 10 ** (-3 * idx)
assert m.write_dist(value) == value
assert m.dist_idx == idx
@pytest.mark.parametrize('labels, unit, error', [
(FloatRange(), '', 'not a datatype'), # 2nd arg must not be a datatype
([(1, 2, 3)], '', 'must be strings'), # label is not a string
([(1, '1V', 3, 4)], 'V', 'labels or tuples'), # 4-tuple
([('1A', 3, 4)], 'A', 'labels or tuples'), # two values after label
(('1m', (0, '1k')), '', 'conflicts with'), # two times index 0
(['1mV', '10mA'], 'V', 'not the form'), # wrong unit
(['.mV'], 'V', 'not the form'), # bad number
(['mV'], 'V', 'not the form'), # missing number
(['1+mV'], 'V', 'not the form'), # bad number
])
def test_bad_float_enum(labels, unit, error):
with pytest.raises(ProgrammingError, match=error):
class Mod(Module): # pylint:disable=unused-variable
param = FloatEnumParam('', labels, unit)

View File

@ -23,8 +23,6 @@
import sys
import threading
import importlib
from glob import glob
import pytest
from frappy.datatypes import BoolType, FloatRange, StringType, IntRange, ScaledInteger
@ -442,12 +440,12 @@ def test_override():
assert Mod.value.value == 5
assert Mod.stop.description == "no decorator needed"
class Mod2(Mod):
class Mod2(Drivable):
@Command()
def stop(self):
pass
# inherit doc string
assert Mod2.stop.description == Mod.stop.description
assert Mod2.stop.description == Drivable.stop.description
def test_command_config():
@ -922,24 +920,3 @@ def test_interface_classes(bases, iface_classes):
pass
m = Mod('mod', LoggerStub(), {'description': 'test'}, srv)
assert m.interface_classes == iface_classes
all_drivables = set()
for pyfile in glob('frappy_*/*.py'):
module = pyfile[:-3].replace('/', '.')
try:
importlib.import_module(module)
except Exception as e:
print(module, e)
continue
for obj_ in sys.modules[module].__dict__.values():
if isinstance(obj_, type) and issubclass(obj_, Drivable):
all_drivables.add(obj_)
@pytest.mark.parametrize('modcls', all_drivables)
def test_stop_doc(modcls):
# make sure that implemented stop methods have a doc string
if (modcls.stop.description == Drivable.stop.description
and modcls.stop.func != Drivable.stop.func):
assert modcls.stop.func.__doc__ # stop method needs a doc string