182 Commits

Author SHA1 Message Date
077a182b4d frappy_psi.cryoltd (flame): improvements
- better error handling
- make persistency mode persistent
- read setpoint from HW
2025-11-25 14:03:51 +01:00
3e7e53135c KJ35: changed flame magnet default tolerance 2024-07-12 09:46:51 +02:00
57b567f453 move magnet from flamedil bananapi to flamemag raspberry 2024-06-12 11:19:18 +02:00
l_samenv
2b42e3fa0a flamesample: improve comments to parmod.SwitchDriv 2024-02-23 10:13:31 +01:00
l_samenv
5b0da3ba98 fixes on 2023-11-27
- ls372 autorange: wait one sec. more for switching
- keep only one channel, even after target is reached
- intermediate target only when T is raise, but not when lowered
2024-02-23 10:13:05 +01:00
l_samenv
c80b4ac5fb fixes for flamesample
- fixes in SwitchDrive
- increase range when reading is zero in autorange
- add debugging log msgs
2024-02-23 10:10:38 +01:00
l_samenv
8cb9154bb5 flamesample: use odd fraction for limits
workaround for problems when driving exactly to the limit
2024-02-23 10:10:29 +01:00
l_samenv
813d1b76ef remove 1K plate heater configuration
this heater does not exist
2024-02-19 12:47:52 +01:00
Alexander Zaft
183709b7ce frappy_mlz seop: add count to ampl and phase cmds
Change-Id: Id7faca31269bb481ec4010f1e0aec2591f0299d6
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/32030
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Alexander Zaft <a.zaft@fz-juelich.de>
2024-02-02 14:25:27 +01:00
2cdf1fc58e fix import order in entangle.py
import order

Change-Id: I54450bb64cb5cbea3d29072b6095b8b9e3962aa6
2024-02-02 14:21:44 +01:00
ffaa9c83bd introduce FrozenParam
For the case when the readback of a parameter does not reflect the
change immediately.

May also be used on Writable.target or Drivable.target with a short
BUSY period.

+ bug fix in an error message in frappy.datatypes.IntRange

Change-Id: I5e1c871629f9e3940ae80f35cb6307f404202b4a
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/31981
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
2024-02-02 14:11:25 +01:00
f9a0fdf7e4 add frappy_psi/calcurve.py and calibtest.py
Change-Id: I5b9aae0ac3bcd76d846c08717201e6c32df4b675
2024-01-31 17:09:17 +01:00
7dfb2ff4e3 logdif.py: only one commit needs to be new enough
+ fixe intendation

Change-Id: I4b0c393767925532e1f105e80a215839a02214af
2024-01-29 15:48:43 +01:00
84c0017c03 synced most of wip to mlz
Change-Id: Ifc5eb0d8ccf693535ab474553759f5622b3a3c8f
2024-01-29 14:31:13 +01:00
2126956160 adopt missing changes while cherry-picking from mlz
Change-Id: Icda4d581e8f0ebd22fc22f2661965bf98a821a34
2024-01-29 14:29:23 +01:00
4cdd3b0709 remove more coding cookies
mainly from frappy_psi

Change-Id: I192811459aebe97f3076888cd31a308a51e6aa49
2024-01-29 14:14:09 +01:00
Alexander Zaft
15d38d7cc1 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 14:06:06 +01:00
Jenkins system
9904d31f0b [deb] Release v0.18.1 2024-01-29 13:51:25 +01:00
Alexander Zaft
b07d2ae8a3 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 13:51:20 +01:00
Alexander Zaft
7d7cb02f17 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 13:51:15 +01:00
Jenkins system
1017925ca0 [deb] Release v0.18.0 2024-01-29 13:51:10 +01:00
bb14d02884 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 13:51:04 +01:00
4c499cf048 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 13:50:50 +01:00
e403396941 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 13:50:00 +01:00
5b42df4a5e 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 13:49:55 +01:00
841ef224f6 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 13:49:39 +01:00
8142ba746d 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 13:49:30 +01:00
Alexander Zaft
5358412b7a 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 13:49:21 +01:00
010f0747e1 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 13:49:08 +01:00
Alexander Zaft
047c52b5a5 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 13:48:37 +01:00
Alexander Zaft
f846c5cb31 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 13:47:18 +01:00
Alexander Zaft
0e4a427bc3 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 13:47:09 +01:00
Alexander Zaft
2d8b609a3c 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 13:47:00 +01:00
Alexander Zaft
6e3865b345 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 13:46:47 +01:00
0004dc7620 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 13:36:18 +01:00
158477792f 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 13:34:45 +01:00
fd0e762d18 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 13:34:39 +01:00
Alexander Zaft
a16ec6cc91 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 13:34:29 +01:00
Alexander Zaft
777a2cb6a9 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 13:33:47 +01:00
cb3e98f86d added logdif.py
a tool the compare commits in branches

Change-Id: I503941b76bb567ea4c3d33b986406a910154fda6
2024-01-29 12:58:45 +01:00
a8bafde64e add test cfg for lockin 7270
Change-Id: Ic6d66b625feb44e8130266901f1296adcb11f532
2024-01-29 10:58:21 +01:00
36c512d50b frappy_psi/SR.py: got changes from develop branch
+ move soft auto range from read_value to doPoll

Change-Id: I0bf8ac15f8515e55cd9131be33615908ffc99c12
2024-01-29 10:29:33 +01:00
Oksana Shliakhtun
17b7a01ce1 Driver for ThermoHaake Phoenix P1 Circulator
Change-Id: I0573eeac2e40b4715072661c819701186733bf94
2024-01-29 10:29:33 +01:00
be66faa591 frappy_psi/thermofisher: version through gerrit
Change-Id: I6999e84d1c5efd0625c6df89e97dad46e5a8cd59
2024-01-29 10:29:33 +01:00
e27b4f72b5 newset version of oksanas drivers
Change-Id: Ia6d8b727e48e96a14b75feeef5d3e6c002cb82a0
2024-01-29 10:29:33 +01:00
l_samenv
bc7922f5c8 iono pi max demo (drums)
+ fix spacing in ionopimax.py
2024-01-25 09:40:10 +01:00
l_samenv
99a58933ec ionopimax: Add LogVoltageInput 2024-01-18 08:40:33 +01:00
9e000528d2 add vacuum furnace cfg file 2024-01-11 16:33:06 +01:00
4a2ce62dd8 added drivers for small furnace 2024-01-11 16:28:06 +01:00
9e6699dd1e more robust calculation for heater resistivity
and check is is in the allowed range 10 .. 100 Ohm

Change-Id: If485480c0974d953165c37f7354dc2818f68b30b
2023-12-15 16:00:26 +01:00
Oksana Shliakhtun
416cdd5a88 Autogain function for SR830 lock-in driver
Change-Id: If07ec9182e5153e1237b9818ce555162f54e0ae5
2023-12-11 08:31:43 +01:00
Oksana Shliakhtun
1bd188e326 For the lockin830 get_par/set_par are implemented.
Change-Id: I5b6707a07d936d24528173a2edae49a148081ff9
2023-12-11 08:31:36 +01:00
Oksana Shliakhtun
f7b29ee959 SR830: moved dicts out of class
Change-Id: If056b1bf4e81c3b609ded087dff2b40c7119903f
2023-12-11 08:31:29 +01:00
Oksana Shliakhtun
f6a0ccb38b Changed write_range, write_tc methods
Change-Id: I335f97bd54deaccf0552b27deb3a7dfe73074e4c
2023-12-11 08:31:17 +01:00
Oksana Shliakhtun
b93a0cd87b New driver for lock-in amplifier SR830
Change-Id: I45c5a06460f4b84cade0eae53188b058510c4473
2023-12-11 08:31:11 +01:00
be6ba73c89 workaround for bug in sea
fix double slash in hdb path

Change-Id: I68ab79c5240abb9fcccbbe5f817f740df2bb5ea6
2023-12-05 14:10:03 +01:00
c075738584 ips_mercury: add NOT_FOUND action 2023-12-04 15:47:18 +01:00
0fa2e8332d do not complain when no output module is configured 2023-12-04 15:46:50 +01:00
afb49199a1 fixes in mb11/dil5 cfg files
- add flowpars
- increase om range to -360
- add sea config
2023-12-04 15:45:00 +01:00
416fe6ddc0 frappy.client: fix the case then timestamp is missing
the previous version failed when timestamp was missing

Change-Id: I77e1fb81b19fb4ee2749d731bafacbac46132f8e
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/32404
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-11-30 09:17:11 +01:00
e3cb5d2e60 frappy.io: change default to retry_first_idn=True
Looked at this code again, and wondered why the default is not True.
It is far more probable that the programmer just forgets to set
this property to True than it would harm to do so.

Change-Id: I439aedbdfc9c2b12737e3ce1694e90550ddf0e78
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/32270
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-11-30 09:16:57 +01:00
l_samenv
998367a727 Merge branch 'wip' of gitlab.psi.ch:samenv/frappy into wip 2023-11-23 15:57:17 +01:00
l_samenv
ab918a33ae cc: replace bool by enum in cc.hav and cc.nav 2023-11-23 15:56:23 +01:00
l_samenv
397ec2efbd add pdld laser driver
2 modules: a switch (on/off) and the power (set: target, readback:value)
2023-11-14 09:17:11 +01:00
67032ff59b ma6: set backlash 2023-10-27 15:11:19 +02:00
03c356590b frappy_psi.phytron: implement limit switches 2023-10-02 16:58:09 +02:00
06bec41ed3 ma6: make ts drivable 2023-10-02 16:56:23 +02:00
4cd6929d4b fix ma15 sea config 2023-10-02 16:55:42 +02:00
a89f7a3c44 configs for sample heat stick 2023-10-02 16:54:52 +02:00
a4330081b7 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-02 13:49:08 +02:00
3b997d7d86 Merge branch 'wip' of gitlab.psi.ch-samenv:samenv/frappy into wip 2023-09-22 13:29:23 +02:00
612295d360 add ill2 2023-09-22 13:29:04 +02:00
9e39a43193 fix fs config 2023-09-22 13:27:54 +02:00
6adfafaa27 more consistent ori1 stick json file 2023-09-22 13:27:10 +02:00
f6c4090b96 fix simulation
+ some fixed in sim_uniax

Change-Id: Ia8703ed988aa904bb2694339f0d3175b28fcb33e
2023-09-19 16:05:52 +02:00
ecef2b8974 more cfg file fixes
Change-Id: I0ba86cd17bb07f480cac6f20994ee854c6e811ae
2023-09-19 15:04:02 +02:00
l_samenv
96a7e2109b cleanup cfg files 2023-09-19 14:43:48 +02:00
l_samenv
2f3c68a5c5 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-09-19 14:17:08 +02:00
l_samenv
e9a195d61e flamedil as of 2023-07-04 2023-09-19 14:17:08 +02:00
l_samenv
6ac3938b78 flamedil as of 2023-07-03 2023-09-19 14:17:08 +02:00
l_samenv
b4cfdcfc1a flame sample combined T 2023-09-19 14:16:21 +02:00
l_samenv
d32fb647a6 frappy_psi.ls372: add TemperatureSensor and TemperatureLoop 2023-09-19 14:14:12 +02:00
l_samenv
abf7859fd6 frappy_psi.cryoltd: fixes after frappy upgrade 2023-09-19 14:14:12 +02:00
l_samenv
55ea2b8cc4 frappy_psi.triton: try to fix channel selection before condense action 2023-09-19 14:14:12 +02:00
27600e3ddf fix bad cfg files
Change-Id: Iacba12a2679777dd4ea2892751d82a63221b1361
2023-09-19 14:07:20 +02:00
l_samenv
6b4244f071 Merge branch 'wip' of gitlab.psi.ch:samenv/frappy into wip 2023-09-19 11:01:06 +02:00
1d81fc6fcd 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-09-19 10:58:07 +02:00
dfce0bdfbc 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-09-19 10:57:59 +02:00
c39aef10aa Merge branch 'wip' of gitlab.psi.ch-samenv:samenv/frappy into wip 2023-09-14 11:10:12 +02:00
45dd87060b 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-09-14 09:10:39 +02:00
8019b359c4 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-09-14 09:09:25 +02:00
4c5109e5a3 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-09-14 09:09:09 +02:00
bf4b3e5683 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-09-14 09:09:00 +02:00
af34fef1e1 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-09-14 09:08:50 +02:00
5e1c22ba28 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-09-14 09:08:36 +02:00
0bc4a63aa7 add parmod.Par
the reasonly class frappy_psi.parmod.Par represents a parameter
or a component of a tuple parameter

Change-Id: I47208c9d7a6fc377cd56b82cc6a9e8cdb433fe8e
2023-09-14 09:05:00 +02:00
cb2c10655c 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-09-13 17:22:58 +02:00
6c49abea74 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-09-11 14:12:14 +02:00
dee8f8929e Merge branch 'wip' of gitlab.psi.ch-samenv:samenv/frappy into wip 2023-09-08 10:46:13 +02:00
2e143963df stickmotor addon: add backlash -1 2023-09-08 10:45:18 +02:00
4bc82c2896 Merge branch 'wip' of gitlab.psi.ch-samenv:samenv/frappy into wip 2023-09-06 08:39:22 +02:00
833a68db51 ma10: improve sea cfg 2023-09-06 08:38:49 +02:00
b9f046a665 hvolt_short stick: make hcp writable 2023-09-06 08:38:06 +02:00
9d9b5b2694 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-08-28 14:17:26 +02:00
255adbf8d9 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-08-25 08:21:16 +02:00
Alexander Zaft
bc0133f55a 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-08-18 16:32:16 +02:00
Alexander Zaft
09e59b93d8 Revert "add zapf to requirements-dev.txt"
This reverts commit e67a46cd01.

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-08-18 16:32:16 +02:00
2474dc5e72 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-08-18 16:32:16 +02:00
Alexander Zaft
9dab41441f 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-08-18 16:32:16 +02:00
Alexander Zaft
4af46a0ea2 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-08-18 16:32:16 +02:00
Alexander Zaft
b844b83352 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-08-18 16:32:16 +02:00
Alexander Zaft
3b63e32395 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-08-18 16:32:16 +02:00
Alexander Zaft
5168e0133d 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-08-18 16:32:16 +02:00
Alexander Zaft
9ea6082ed8 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-08-18 16:32:16 +02:00
Alexander Zaft
f205cf76aa 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-08-18 16:32:16 +02:00
db9ce02028 Revert "revert commits done before MZ holidays"
This reverts commit d2885bdd72.
2023-08-18 16:32:16 +02:00
dmc
c4a39306e4 Merge branch 'wip' of gitlab.psi.ch-samenv:samenv/frappy into wip 2023-08-17 14:10:29 +02:00
dmc
024de0bd32 insert pressure reading into ccrpe_cfg 2023-08-17 14:07:44 +02:00
d2d63c47e1 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-08-14 14:32:01 +02:00
565e8e6fd3 Merge branch 'wip' of gitlab.psi.ch-samenv:samenv/frappy into wip 2023-08-10 14:35:03 +02:00
89bc7f6dfe add ori7 2023-08-10 14:34:51 +02:00
c69fe1571a add hvolt_short 2023-08-09 17:39:19 +02:00
c40033a816 update old cfg files
- change secop_psi to frappy_psi
- remove interface and name in Node

Change-Id: I69242de250c9ecf52e001fce6396347dbf3fedcb
2023-08-09 17:36:02 +02:00
da37175cbb 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-08-07 16:15:30 +02:00
2020928289 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-08-07 16:15:30 +02:00
9df6794678 fix haake_cfg 2023-08-07 16:10:55 +02:00
41f3b7526e fix ma7.config.json 2023-08-07 16:10:30 +02:00
l_samenv
f80624b48d MA7: add unit=T to mf 2023-07-11 15:36:14 +02:00
l_samenv
9e2e6074c8 Merge branch 'wip' of gitlab.psi.ch:samenv/frappy into wip 2023-07-11 13:27:57 +02:00
5a13888498 sMA6 encoder mode to CHECK
after Oksana experienced that it works
2023-07-11 11:27:20 +02:00
5a8a6b88ff 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-07-11 10:59:42 +02:00
b84b7964e3 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-07-11 10:59:42 +02:00
l_samenv
6c5dddc449 ma7: sea confg: make ta/tb visible 2023-07-10 10:55:43 +02:00
78fa49ef74 Merge branch 'wip' of gitlab.psi.ch-samenv:samenv/frappy into wip 2023-07-07 15:46:05 +02:00
d92b154292 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-07-07 15:40:20 +02:00
073fe1a08b frappy.io: make error reporting consistent
- fix mechanism to avoid multiple error messages in log files

Change-Id: I688071f9b06da1a81eb12d63adb549042171c7c8
2023-07-07 15:40:20 +02:00
f80c793cd9 consmetic changes to ma6_sample_heat_cfg.py 2023-07-06 14:49:06 +02:00
519e9e2ed7 MA6: set om.encoder_mode to 'NO' 2023-07-06 14:48:09 +02:00
14036160f7 add special configurations m6/ma7 sampleheat 2023-07-06 13:06:06 +02:00
131dc60807 MA7/MA11: make ts drivable 2023-07-06 13:04:36 +02:00
49722a858f update haake + eurotherm cfg 2023-07-06 13:04:11 +02:00
c61b674382 disable encoder for MA11 stick rotation 2023-07-06 12:57:07 +02:00
091543be56 add FW (old power rack, via SEA) 2023-07-06 12:53:19 +02:00
d2885bdd72 revert commits done before MZ holidays
they are all not neccessary for SINQ SE operation

Change-Id: Ic9adcccf685752ab90bb6b86005ac8e04b302855
2023-07-06 08:03:15 +02:00
975593dd6b update to gerrit version
Change-Id: Ifdaa28dd961a529cd9197c4c3639744f108b0a6a
2023-07-05 17:33:05 +02:00
Alexander Zaft
4fe28363d3 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-07-05 17:16:41 +02:00
28b19dbf57 frappy_psi.thermofisher: add version through gerrit
Change-Id: I4b89d6ec803ad64c41720bc62493d2e4027df50e
2023-07-05 17:14:07 +02:00
05189d094a 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-07-05 17:12:12 +02:00
47da14eef9 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-07-05 17:11:40 +02:00
Alexander Zaft
7904f243cb 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-07-05 17:10:56 +02:00
a2fed8df03 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-07-05 17:08:48 +02:00
19f965bced Merge branch 'wip' of gitlab.psi.ch-samenv:samenv/frappy into wip 2023-07-03 17:51:43 +02:00
3e4ea2515e add ma15 cfg 2023-07-03 17:51:28 +02:00
714c820115 fixes in ori3 and dil5 config 2023-07-03 17:49:06 +02:00
a8e1d0e1e8 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-06-29 11:28:04 +02:00
l_samenv
d7a1604bd5 frappy_psi.sea: auto connect
on both .ssynio and /syncio try to reconnect after failure
2023-06-26 14:45:53 +02:00
b92095974b camea filter addon
Change-Id: I1d80aa3bfc4e441ad8a69930b81d6cc25cee9511
2023-06-20 11:05:15 +02:00
Alexander Zaft
8dc9c57e9d entangle: fix tango guards for pytango 9.3
Change-Id: I666969f9c798971d5cd8a0c2f6564067ac3cde72
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/31327
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Georg Brandl <g.brandl@fz-juelich.de>
2023-06-20 09:00:42 +02:00
Alexander Zaft
7c95f1f8ee config: fix merge_modules
Change-Id: I31d05afe300443e08fb08f9e6645401f52cfae39
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/31323
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Alexander Zaft <a.zaft@fz-juelich.de>
2023-06-20 08:05:55 +02:00
3786d2f209 frappy_psi.triton: fix HeaterOutput.limit
+ fix handling of control_active

Change-Id: Ic11933f6c1c4d9df07aa9d06ae4dca40b755e4ed
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/31377
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
2023-06-20 08:03:37 +02:00
138b84e84c add a hook for reads to be done initially
inital reads from HW should be done in the thread started by
startModule, not in startModule itself.

- add a hook method 'initialReads' for this
+ add doc for init methods
+ fix some errors in doc

Change-Id: I914e3b7ee05050eea1ee8aff3461030adf08a461
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/31374
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-06-20 08:03:37 +02:00
997e8e26e9 frappy_psi.mercury: proper handling of control_active
Change-Id: I31e846fa6fdf6d642184e3736a66ffd53033bccf
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/31376
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-06-20 08:03:37 +02:00
644d005dad frappy.mixins.HasOutputModule
add 'set_control_active' method for overriding by subclasses

Change-Id: Ib344319862a4a0bf29efef16a63db09d1f314a82
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/31375
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-06-20 08:03:37 +02:00
36dfe968e8 frappy_psi.phytron: rename reset_error to clear_errors
use the command 'clear_errors' to return from an error state

+ make sure target is valid after clear_errors

Change-Id: I3c180500a05836d52bbb9a8ecbdb397adea03d0d
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/31337
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
2023-06-20 08:03:37 +02:00
0932228596 frappy_psi.mercury/triton: add control_off command
frappy_psi.triton.TemperatureLoop has not output module to
deactivate control -> add control_off also to loops in
frappy_psi.mercury

Change-Id: I4dc4333134da34a8d3ae0f3c037a1e5b108c95a1
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/31341
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-06-20 08:03:37 +02:00
Alexander Zaft
dff0c819de io: followup fix for retry-first-ident
followup fix: no error was raised ever for the first identification
message.

Change-Id: I80f0f431add6dfd7b37d750b9fc661174aa8f217
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/31318
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-06-20 08:02:13 +02:00
Alexander Zaft
fd917724d8 io: add option to retry first ident request
Change-Id: I524c15387eaf2461e3dfe690250a55f058467b0b
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/31291
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Bjoern Pedersen <bjoern.pedersen@frm2.tum.de>
Reviewed-by: Alexander Zaft <a.zaft@fz-juelich.de>
2023-06-20 08:02:02 +02:00
bf43858031 GUI bugfix: use isChecked instead of checkState in BoolInput
Change-Id: I4896df13c117c6eeaaaaba80ca3da4b1982c3d9b
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/31346
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-06-16 11:09:27 +02:00
f354b19cf0 frappy_psi.sea: fix extra_module_set
Change-Id: If5669fdd60c8505a47414f17cfcd8534cdc2abee
2023-06-06 16:50:48 +02:00
f304ac019e fix cfg files (extra_modules/single_module)
Change-Id: I1821e1e0dc960d48a3e343c53195808798b7f969
2023-06-06 16:49:54 +02:00
9a9a22588f Merge branch 'wip' of gitlab.psi.ch-samenv:samenv/frappy into wip 2023-06-06 16:46:31 +02:00
3e26dd49d0 jtccr: fix cfg for extra_modules / single_module 2023-06-06 16:45:31 +02:00
5a456a82b0 fix enum when SEA type is text
Change-Id: I873045a2dac8771b844431ccda70ce1b8ff1aee5
2023-06-05 13:56:00 +02:00
f6868da3b9 fix systemd bug
Change-Id: I8a3f1eddba9525589757d4612a5060267ea0c5db
2023-06-05 13:56:00 +02:00
ee31f8fb45 fix merge_status in HasConvergence 2023-06-05 13:03:38 +02:00
a6a3f80e30 remove sign=-1 from cfg files 2023-06-05 13:02:40 +02:00
ad36ab1067 frappy_psi.thermofisher improvements
- merge Loop with Sensor
- make convergence work

Change-Id: Iba0cafc524ada6d490b7a5c30f4127e77fd163f3
2023-06-05 09:50:35 +02:00
f2d795cfba frappy_psi.convergence: improvments
- merge_status
- empty string instead of 'approaching'
- dif <= tol

Change-Id: I6f10875f7ef5d2109c13d7448ede114b8e30d86e
2023-06-05 09:47:08 +02:00
c04337c3a4 frappy.client: missing exception method in dummy logger
Change-Id: Ie3a574c3060f2ac6833ff44e8074a19db6ea2f0b
2023-06-05 09:45:04 +02:00
57d5298c92 remove secop-server.spec
Change-Id: I8097a2918bc4e786bd270aeb436efebe9a3bd88f
2023-05-31 14:32:04 +02:00
9a6421a54f up to date with develop/mlz
Change-Id: I5ea71bc99a2f0dffc3dbe37e1119eb188ef8a3f0
2023-05-31 14:27:36 +02:00
c5d429346d update 2023-05-30 from gitmlz
Change-Id: I0b1eb2941692fde5c9d98f107fc38315625dcfdb
2023-05-31 14:16:12 +02:00
24 changed files with 855 additions and 869 deletions

22
calibtest.py Normal file
View File

@@ -0,0 +1,22 @@
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://ldm-fi-ts:3001',
uri='tcp://ldmcc01-ts:3004',
)
Mod('T',

View File

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

View File

@@ -24,6 +24,7 @@ Mod('ts_low',
minrange=13,
range=22,
tolerance = 0.1,
vexc = 3,
htrrng=4,
)
@@ -32,7 +33,8 @@ Mod('ts_high',
'sample Cernox',
channel = 1,
switcher = 'lsc_channel',
minrange=9,
minrange=11,
vexc = 5,
range=22,
tolerance = 0.1,
htrrng=5,
@@ -45,6 +47,8 @@ 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,16 +0,0 @@
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',
)

View File

@@ -1,12 +1,14 @@
Node('flamemag.psi.ch',
'flame magnet',
interface='tcp://5000'
interface='tcp://5000',
)
sea_cfg = 'flamemag.config'
Mod('cio',
'frappy_psi.cryoltd.IO',
'IO to cryo ltd software',
uri='tcp://flamedil:3128',
uri='tcp://flamemag:3128',
)
Mod('main',
@@ -24,7 +26,7 @@ Mod('B',
target=Param(
max=35000.0,
),
mode='PERSISTENT',
#mode='PERSISTENT',
hw_units='T',
A_to_G=285.73,
ramp=Param(
@@ -32,7 +34,7 @@ Mod('B',
),
overshoot={'o': 1.0, 't': 180.0},
degauss={'s': 500.0, 'd': 30.0, 'f': 5.0, 't': 120.0},
tolerance=5.0,
tolerance=50.0,
wait_switch_on = 30,
wait_switch_off = 30,
wait_stable_field=180.0,

View File

@@ -1,34 +0,0 @@
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,67 +0,0 @@
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,13 +138,6 @@ 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

@@ -36,12 +36,12 @@ from time import sleep, time as currenttime
import PyTango
from frappy.datatypes import ArrayOf, EnumType, FloatRange, IntRange, \
LimitsType, StringType, TupleOf, ValueType
LimitsType, StatusType, 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, Readable, \
StatusType, Writable, Property
from frappy.modules import Command, Drivable, Module, Parameter, Property, \
Readable, Writable
#####

View File

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

View File

@@ -1,229 +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: 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,7 +15,6 @@
#
# Module authors: Oksana Shliakhtun <oksana.shliakhtun@psi.ch>
# *****************************************************************************
"""Stanford Research Systems SR830 DS Lock-in Amplifier"""
import re
import time
@@ -26,11 +25,6 @@ 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}
@@ -46,13 +40,6 @@ 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)
@@ -162,10 +149,6 @@ 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')
@@ -183,13 +166,11 @@ 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()
@@ -197,12 +178,6 @@ 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')
@@ -219,7 +194,6 @@ 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):
@@ -227,18 +201,11 @@ 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

@@ -1,279 +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: 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?')

576
frappy_psi/calcurve.py Normal file
View File

@@ -0,0 +1,576 @@
#!/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

@@ -29,7 +29,8 @@ import re
import time
from math import copysign
from frappy.core import HasIO, StringIO, Readable, Drivable, Parameter, Command, \
Module, Property, Attached, Enum, IDLE, BUSY, ERROR
Module, Property, Attached, Enum, IDLE, BUSY, ERROR, nopoll, PersistentParam, \
PersistentMixin
from frappy.errors import ConfigError, BadValueError, HardwareError
from frappy.datatypes import FloatRange, StringType, EnumType, StructOf
from frappy.states import HasStates, status_code, Retry
@@ -41,7 +42,10 @@ VALUE_UNIT = re.compile(r'([-0-9.E]*\d|inf)([A-Za-z/%]*)$')
def as_float(value):
"""converts string (with unit) to float"""
return float(VALUE_UNIT.match(value).group(1))
try:
return float(VALUE_UNIT.match(value).group(1))
except Exception:
raise ValueError(f'can not convert {value!r} to float with unit')
BOOL_MAP = {'TRUE': True, 'FALSE': False}
@@ -113,6 +117,8 @@ class Main(HasIO, Module):
# ignore multiline values
# if needed, we may collect here and treat with a special key
continue
if not value:
continue # silently ignore empty values
obj, pname, cvt = self.params_map.get(key, missing)
if obj:
if not hasattr(obj, pname):
@@ -268,7 +274,7 @@ class BaseMagfield(HasStates, Channel):
def cvt_error(self, text):
if text != self._last_error:
self._last_error = text
self.log.error(text)
self.log.error(f'{self.channel}_Error: {text}')
return text
return self._error_text
@@ -287,6 +293,11 @@ class BaseMagfield(HasStates, Channel):
ramp = Parameter()
target = Parameter()
@nopoll
def read_setpoint(self):
self.main.doPoll()
return self.setpoint
def write_ramp(self, ramp):
if self._rate_units != 'A/s':
self.sendcmd('Set:<CH>:ChangeRateUnits A/s')
@@ -323,9 +334,18 @@ class BaseMagfield(HasStates, Channel):
return super().start_field_change
def start_ramp_to_target(self, sm):
self.start_sweep(sm.target)
try:
self.start_sweep(sm.target)
self.log.info('start_ramp_to_target: start_sweep done')
except Exception as e:
self.log.error('start_ramp_to_target: start_sweep failed with %r', e)
raise
return self.ramp_to_target # -> stabilize_field
# def start_ramp_to_target(self, sm):
# self.start_sweep(sm.target)
# return self.ramp_to_target # -> stabilize_field
def stabilize_field(self, sm):
if self._ready_text == 'FALSE':
# wait for overshoot/degauss/cycle
@@ -432,7 +452,7 @@ class BaseMagfield(HasStates, Channel):
self._error_text = ''
class MainField(BaseMagfield, magfield.Magfield):
class MainField(PersistentMixin, BaseMagfield, magfield.Magfield):
checked_modules = None
def earlyInit(self):
@@ -456,12 +476,13 @@ class MainField(BaseMagfield, magfield.Magfield):
super().check_limits(value)
self.check_combined(None, 0, value)
mode = Parameter(datatype=EnumType(PersistencyMode))
mode = PersistentParam(datatype=EnumType(PersistencyMode))
def write_mode(self, mode):
self.reset_error()
super().write_mode(mode) # updates mode
return mode
self.mode = mode
self.saveParameters()
@status_code('PREPARING')
def start_ramp_to_field(self, sm):

123
frappy_psi/frozenparam.py Normal file
View File

@@ -0,0 +1,123 @@
# *****************************************************************************
# 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

@@ -15,8 +15,6 @@
#
# 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, \
@@ -24,13 +22,7 @@ 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)
@@ -63,21 +55,11 @@ 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()
@@ -90,10 +72,6 @@ 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:
@@ -101,11 +79,6 @@ 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
@@ -126,11 +99,6 @@ 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')
@@ -138,11 +106,6 @@ 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')
@@ -150,7 +113,7 @@ class TemperatureLoop(HasIO, HasConvergence, Drivable):
@Command
def clear_errors(self):
""" Reset after error. Otherwise the status will not be updated"""
""" Reset after error"""
if self.read_status()[0] == ERROR:
try:
self.communicate('ER')

View File

@@ -231,14 +231,17 @@ 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]:
@@ -251,8 +254,10 @@ 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
@@ -381,6 +386,10 @@ 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

@@ -112,7 +112,7 @@ class SimpleMagfield(HasStates, Drivable):
last = self._last_target
if last is None:
try:
last = self.setpoint # get read back from HW, if available
last = self.read_setpoint() # get read back from HW, if available
except Exception:
pass
if last is None or abs(last - self.value) > self.tolerance:

View File

@@ -22,7 +22,7 @@
"""modules to access parameters"""
from frappy.core import Drivable, EnumType, IDLE, Attached, StringType, Property, \
Parameter, FloatRange, Readable, ERROR
Parameter, FloatRange, BoolType, Readable, ERROR
from frappy.errors import ConfigError
from frappy_psi.convergence import HasConvergence
from frappy_psi.mixins import HasRamp
@@ -148,51 +148,61 @@ 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)
_switch_target = None # if not None, switch to selection mhen mid range is reached
autoswitch = Parameter('switch sensor automatically', BoolType(), readonly=False, default=True)
_switch_target = None # if not None, switch to selection when mid range is reached
# TODO: copy units from attached module
# TODO: callbacks for updates
def doPoll(self):
super().doPoll()
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:
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:
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:
if not low_valid and not self.low.control_active:
set_enabled(self.low, False)
return
if low_valid and low < self.min_high:
if not high_valid:
if not high_valid and not self.high.control_active:
set_enabled(self.high, False)
return
set_enabled(self.low, True)
set_enabled(self.high, True)
# 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
def read_value(self):
return self.low.value if self.selected == LOW else self.high.value
return self.low.value if self.get_selected() == LOW else self.high.value
def read_status(self):
status = self.low.status if self.selected == LOW else self.high.status
status = self.low.status if self.get_selected() == LOW else self.high.status
if status[0] >= ERROR:
return status
return super().read_status() # convergence status
@@ -202,22 +212,23 @@ class SwitchDriv(HasConvergence, Drivable):
selected = self.selected
target1 = target
self._switch_target = None
if target > self.max_low:
if target > self.max_low * 0.75 + self.min_high * 0.25:
if self.value < self.min_high:
target1 = self.max_low
target1 = min(target, self.max_low)
self._switch_target = HIGH
selected = LOW
else:
this, other = other, this
selected = HIGH
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 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 self.selected == HIGH:
this, other = other, this
if hasattr(other, 'control_off'):

View File

@@ -1,74 +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: 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,17 +31,10 @@ class QnwIO(StringIO):
class SensorTC1(HasIO, Readable):
ioClass = QnwIO
value = Parameter(unit='degC', min=-55, max=150)
value = Parameter(unit='degC', min=-15, max=120)
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):
@@ -73,7 +66,7 @@ class SensorTC1(HasIO, Readable):
class TemperatureLoopTC1(SensorTC1, Drivable):
value = Parameter('temperature', unit='degC')
target = Parameter('setpoint', unit='degC', min=-55, max=150)
target = Parameter('setpoint', unit='degC', min=-5, max=110)
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)
@@ -87,16 +80,6 @@ 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

View File

@@ -26,6 +26,7 @@ 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)
@@ -39,22 +40,23 @@ 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 = Parameter('action to do', EnumType(none=0, condense=1, collect=3), readonly=False)
target = FrozenParam('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)
def read_target(self):
return self._target
# 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._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)
return self.change('SYS:DR:ACTN', value, actions_map)
self.change('SYS:DR:ACTN', value, actions_map)
return value
# actions:
# NONE (no action)
@@ -74,7 +76,7 @@ class Action(MercuryChannel, Writable):
class Valve(MercuryChannel, Drivable):
kind = 'VALV'
value = Parameter('valve state', EnumType(closed=0, opened=1))
target = Parameter('valve target', EnumType(close=0, open=1))
target = FrozenParam('valve target', EnumType(close=0, open=1))
_try_count = None
@@ -108,6 +110,10 @@ 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
@@ -120,13 +126,18 @@ class Valve(MercuryChannel, Drivable):
class Pump(MercuryChannel, Writable):
kind = 'PUMP'
value = Parameter('pump state', EnumType(off=0, on=1))
target = Parameter('pump target', EnumType(off=0, on=1))
target = FrozenParam('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):
return self.change('DEV::PUMP:SIG:STATE', value, off_on)
self.change('DEV::PUMP:SIG:STATE', value, off_on)
return value
def read_status(self):
return IDLE, ''