From 71629c1d3a775ba0f246cec0bf3f6d371b4dc6de Mon Sep 17 00:00:00 2001 From: Markus Zolliker Date: Thu, 30 Oct 2025 12:24:34 +0100 Subject: [PATCH] improvements when testing leiden - triple current source - software loop --- cfg/addons/ah2700leiden_cfg.py | 20 +++ cfg/main/leiden370_cfg.py | 110 +++++++++++++++ cfg/sea/leiden370.config.json | 213 +++++++++++++++++++++++++++++ frappy_psi/ls370res.py | 2 +- frappy_psi/picontrol.py | 243 +++++++++++++++++++++++++-------- frappy_psi/tcs.py | 12 +- 6 files changed, 535 insertions(+), 65 deletions(-) create mode 100644 cfg/addons/ah2700leiden_cfg.py create mode 100644 cfg/main/leiden370_cfg.py create mode 100644 cfg/sea/leiden370.config.json diff --git a/cfg/addons/ah2700leiden_cfg.py b/cfg/addons/ah2700leiden_cfg.py new file mode 100644 index 00000000..fba5a738 --- /dev/null +++ b/cfg/addons/ah2700leiden_cfg.py @@ -0,0 +1,20 @@ +Node('ah2700.frappy.psi.ch', + 'Andeen Hagerlin 2700 Capacitance Bridge', +) + +Mod('cap_io', + 'frappy_psi.ah2700.Ah2700IO', + '', + uri='linse-leiden-ts:3002', + timeout=60, +) + +# this creates also cap_freq and cap_loss +Mod('cap', + 'frappy_psi.ah2700.Capacitance', + 'capacitance', + io = 'cap_io', + loss_name='loss', + freq_name='freq', +) + diff --git a/cfg/main/leiden370_cfg.py b/cfg/main/leiden370_cfg.py new file mode 100644 index 00000000..48b9f48a --- /dev/null +++ b/cfg/main/leiden370_cfg.py @@ -0,0 +1,110 @@ +Node('leiden370.config.sea.psi.ch', + '''30 K - Leidendil Pulsetube with ls370''', +) +Mod('sea_main', + 'frappy_psi.sea.SeaClient', + 'main sea connection for leiden370.config', + config = 'leiden370.config', + service = 'main', +) + +for name in ['T3K', 'Tstill', 'T50mK', 'Tmxlow', 'Tmxhigh', 'Tmxcx', 'Tblueo', + 'Tpt50', 'Tpt3high', 'Tpt3low', 'Twhite', 'Tgreen']: + mname = name.replace('T','T_') + Mod(mname, + 'frappy_psi.sea.SeaReadable', '', + io='sea_main', + sea_object='tt', + rel_paths=[name], + value=Param(unit='K'), + extra_modules = ['raw'], + ) + Mod(name.replace('T', 'R_'), + 'frappy_psi.sea.SeaReadable', '', + io='sea_main', + value=Param(unit='Ohm'), + single_module=f'{mname}.raw' + ) + +Mod('cmn', + 'frappy_psi.sea.SeaReadable', '', + io = 'sea_main', + sea_object = 'cmn', + extra_modules = ['u1', 'u2', 'temp'], +) + +Mod('T_cmn', + 'frappy_psi.sea.SeaReadable', '', + io='sea_main', + value=Param(unit='K'), + single_module='cmn.temp', +) + +Mod('V_fixp', + 'frappy_psi.sea.SeaReadable', '', + io='sea_main', + value=Param(unit='V'), + single_module='cmn.u2', +) + +Mod('V_cmn', + 'frappy_psi.sea.SeaReadable', '', + io='sea_main', + value=Param(unit='V'), + single_module='cmn.u1', +) + +Mod('tcs_io', + 'frappy_psi.tcs.IO', + 'tcs communication', + uri='linse-leiden-ts:3005', + ) + +Mod('still_htr', + 'frappy_psi.tcs.Heater', + 'still heater', + io='tcs_io', + channel=2, + ) + +Mod('mix_htr', + 'frappy_psi.tcs.WrappedHeater', + 'mixing chamber heater', + io='tcs_io', + channel=3, + ) + +Mod('drive_mix', + 'frappy_psi.picontrol.PIctrl', + 'controlled temperature ', + input_module = 'T_mxlow', + output_module = 'mix_htr', + output_min = 0, + output_max = 0.02, + p = 5, + itime = 60, + ) + +Mod('drive_cmn', + 'frappy_psi.picontrol.PIctrl', + 'controlled temperature ', + input_module = 'T_cmn', + output_module = 'mix_htr', + output_min = 0, + output_max = 1e-3, + p = 5, + itime = 120, + ) + +Mod('drive_fixp', + 'frappy_psi.picontrol.PI', + 'controlled temperature ', + value=Param(unit='V'), + input_module = 'V_fixp', + output_module = 'drive_mix', + output_min = 0.0, + output_max = 0.01, + p = 1, + itime = 120, + ) + diff --git a/cfg/sea/leiden370.config.json b/cfg/sea/leiden370.config.json new file mode 100644 index 00000000..729e6525 --- /dev/null +++ b/cfg/sea/leiden370.config.json @@ -0,0 +1,213 @@ +{"tt": {"base": "/tt", "params": [ +{"path": "", "type": "int", "kids": 18}, +{"path": "send", "type": "text", "readonly": false, "cmd": "tt send", "visibility": 3}, +{"path": "status", "type": "text", "visibility": 3}, +{"path": "autoscan", "type": "bool", "readonly": false, "cmd": "tt autoscan", "kids": 4}, +{"path": "autoscan/synchronized", "type": "bool", "readonly": false, "cmd": "tt autoscan/synchronized"}, +{"path": "autoscan/interval", "type": "text", "readonly": false, "cmd": "tt autoscan/interval"}, +{"path": "autoscan/pause", "type": "text", "readonly": false, "cmd": "tt autoscan/pause"}, +{"path": "autoscan/dwell", "type": "text", "readonly": false, "cmd": "tt autoscan/dwell"}, +{"path": "T3K", "type": "float", "kids": 14}, +{"path": "T3K/active", "type": "enum", "enum": {"inactive": 0, "active": 1}, "readonly": false, "cmd": "tt T3K/active"}, +{"path": "T3K/autorange", "type": "bool", "readonly": false, "cmd": "tt T3K/autorange", "description": "autorange (common for all channels)"}, +{"path": "T3K/range", "type": "text", "readonly": false, "cmd": "tt T3K/range", "description": "resistance range in Ohm"}, +{"path": "T3K/range_num", "type": "int"}, +{"path": "T3K/excitation", "type": "text", "readonly": false, "cmd": "tt T3K/excitation", "description": "excitation with unit, i.e. 2uV or 3pA"}, +{"path": "T3K/excitation_num", "type": "int"}, +{"path": "T3K/excitation_mode", "type": "enum", "enum": {"voltage": 0, "current": 1, "off": 2}}, +{"path": "T3K/pause", "type": "int", "readonly": false, "cmd": "tt T3K/pause", "description": "pause time [sec] after channel change"}, +{"path": "T3K/filter", "type": "int", "readonly": false, "cmd": "tt T3K/filter", "description": "filter average time [sec]"}, +{"path": "T3K/dwell", "type": "int", "readonly": false, "cmd": "tt T3K/dwell", "description": "dwell time [sec]. Total time per channel: pause + filter + dwell"}, +{"path": "T3K/status", "type": "text"}, +{"path": "T3K/curve", "type": "text", "readonly": false, "cmd": "tt T3K/curve", "kids": 1}, +{"path": "T3K/curve/points", "type": "floatvarar", "readonly": false, "cmd": "tt T3K/curve/points", "visibility": 3}, +{"path": "T3K/alarm", "type": "float", "readonly": false, "cmd": "tt T3K/alarm"}, +{"path": "T3K/raw", "type": "float"}, +{"path": "Tstill", "type": "float", "kids": 14}, +{"path": "Tstill/active", "type": "enum", "enum": {"inactive": 0, "active": 1}, "readonly": false, "cmd": "tt Tstill/active"}, +{"path": "Tstill/autorange", "type": "bool", "readonly": false, "cmd": "tt Tstill/autorange", "description": "autorange (common for all channels)"}, +{"path": "Tstill/range", "type": "text", "readonly": false, "cmd": "tt Tstill/range", "description": "resistance range in Ohm"}, +{"path": "Tstill/range_num", "type": "int"}, +{"path": "Tstill/excitation", "type": "text", "readonly": false, "cmd": "tt Tstill/excitation", "description": "excitation with unit, i.e. 2uV or 3pA"}, +{"path": "Tstill/excitation_num", "type": "int"}, +{"path": "Tstill/excitation_mode", "type": "enum", "enum": {"voltage": 0, "current": 1, "off": 2}}, +{"path": "Tstill/pause", "type": "int", "readonly": false, "cmd": "tt Tstill/pause", "description": "pause time [sec] after channel change"}, +{"path": "Tstill/filter", "type": "int", "readonly": false, "cmd": "tt Tstill/filter", "description": "filter average time [sec]"}, +{"path": "Tstill/dwell", "type": "int", "readonly": false, "cmd": "tt Tstill/dwell", "description": "dwell time [sec]. Total time per channel: pause + filter + dwell"}, +{"path": "Tstill/status", "type": "text"}, +{"path": "Tstill/curve", "type": "text", "readonly": false, "cmd": "tt Tstill/curve", "kids": 1}, +{"path": "Tstill/curve/points", "type": "floatvarar", "readonly": false, "cmd": "tt Tstill/curve/points", "visibility": 3}, +{"path": "Tstill/alarm", "type": "float", "readonly": false, "cmd": "tt Tstill/alarm"}, +{"path": "Tstill/raw", "type": "float"}, +{"path": "T50mK", "type": "float", "kids": 14}, +{"path": "T50mK/active", "type": "enum", "enum": {"inactive": 0, "active": 1}, "readonly": false, "cmd": "tt T50mK/active"}, +{"path": "T50mK/autorange", "type": "bool", "readonly": false, "cmd": "tt T50mK/autorange", "description": "autorange (common for all channels)"}, +{"path": "T50mK/range", "type": "text", "readonly": false, "cmd": "tt T50mK/range", "description": "resistance range in Ohm"}, +{"path": "T50mK/range_num", "type": "int"}, +{"path": "T50mK/excitation", "type": "text", "readonly": false, "cmd": "tt T50mK/excitation", "description": "excitation with unit, i.e. 2uV or 3pA"}, +{"path": "T50mK/excitation_num", "type": "int"}, +{"path": "T50mK/excitation_mode", "type": "enum", "enum": {"voltage": 0, "current": 1, "off": 2}}, +{"path": "T50mK/pause", "type": "int", "readonly": false, "cmd": "tt T50mK/pause", "description": "pause time [sec] after channel change"}, +{"path": "T50mK/filter", "type": "int", "readonly": false, "cmd": "tt T50mK/filter", "description": "filter average time [sec]"}, +{"path": "T50mK/dwell", "type": "int", "readonly": false, "cmd": "tt T50mK/dwell", "description": "dwell time [sec]. Total time per channel: pause + filter + dwell"}, +{"path": "T50mK/status", "type": "text"}, +{"path": "T50mK/curve", "type": "text", "readonly": false, "cmd": "tt T50mK/curve", "kids": 1}, +{"path": "T50mK/curve/points", "type": "floatvarar", "readonly": false, "cmd": "tt T50mK/curve/points", "visibility": 3}, +{"path": "T50mK/alarm", "type": "float", "readonly": false, "cmd": "tt T50mK/alarm"}, +{"path": "T50mK/raw", "type": "float"}, +{"path": "Tmxlow", "type": "float", "kids": 14}, +{"path": "Tmxlow/active", "type": "enum", "enum": {"inactive": 0, "active": 1}, "readonly": false, "cmd": "tt Tmxlow/active"}, +{"path": "Tmxlow/autorange", "type": "bool", "readonly": false, "cmd": "tt Tmxlow/autorange", "description": "autorange (common for all channels)"}, +{"path": "Tmxlow/range", "type": "text", "readonly": false, "cmd": "tt Tmxlow/range", "description": "resistance range in Ohm"}, +{"path": "Tmxlow/range_num", "type": "int"}, +{"path": "Tmxlow/excitation", "type": "text", "readonly": false, "cmd": "tt Tmxlow/excitation", "description": "excitation with unit, i.e. 2uV or 3pA"}, +{"path": "Tmxlow/excitation_num", "type": "int"}, +{"path": "Tmxlow/excitation_mode", "type": "enum", "enum": {"voltage": 0, "current": 1, "off": 2}}, +{"path": "Tmxlow/pause", "type": "int", "readonly": false, "cmd": "tt Tmxlow/pause", "description": "pause time [sec] after channel change"}, +{"path": "Tmxlow/filter", "type": "int", "readonly": false, "cmd": "tt Tmxlow/filter", "description": "filter average time [sec]"}, +{"path": "Tmxlow/dwell", "type": "int", "readonly": false, "cmd": "tt Tmxlow/dwell", "description": "dwell time [sec]. Total time per channel: pause + filter + dwell"}, +{"path": "Tmxlow/status", "type": "text"}, +{"path": "Tmxlow/curve", "type": "text", "readonly": false, "cmd": "tt Tmxlow/curve", "kids": 1}, +{"path": "Tmxlow/curve/points", "type": "floatvarar", "readonly": false, "cmd": "tt Tmxlow/curve/points", "visibility": 3}, +{"path": "Tmxlow/alarm", "type": "float", "readonly": false, "cmd": "tt Tmxlow/alarm"}, +{"path": "Tmxlow/raw", "type": "float"}, +{"path": "Tmxhigh", "type": "float", "kids": 14}, +{"path": "Tmxhigh/active", "type": "enum", "enum": {"inactive": 0, "active": 1}, "readonly": false, "cmd": "tt Tmxhigh/active"}, +{"path": "Tmxhigh/autorange", "type": "bool", "readonly": false, "cmd": "tt Tmxhigh/autorange", "description": "autorange (common for all channels)"}, +{"path": "Tmxhigh/range", "type": "text", "readonly": false, "cmd": "tt Tmxhigh/range", "description": "resistance range in Ohm"}, +{"path": "Tmxhigh/range_num", "type": "int"}, +{"path": "Tmxhigh/excitation", "type": "text", "readonly": false, "cmd": "tt Tmxhigh/excitation", "description": "excitation with unit, i.e. 2uV or 3pA"}, +{"path": "Tmxhigh/excitation_num", "type": "int"}, +{"path": "Tmxhigh/excitation_mode", "type": "enum", "enum": {"voltage": 0, "current": 1, "off": 2}}, +{"path": "Tmxhigh/pause", "type": "int", "readonly": false, "cmd": "tt Tmxhigh/pause", "description": "pause time [sec] after channel change"}, +{"path": "Tmxhigh/filter", "type": "int", "readonly": false, "cmd": "tt Tmxhigh/filter", "description": "filter average time [sec]"}, +{"path": "Tmxhigh/dwell", "type": "int", "readonly": false, "cmd": "tt Tmxhigh/dwell", "description": "dwell time [sec]. Total time per channel: pause + filter + dwell"}, +{"path": "Tmxhigh/status", "type": "text"}, +{"path": "Tmxhigh/curve", "type": "text", "readonly": false, "cmd": "tt Tmxhigh/curve", "kids": 1}, +{"path": "Tmxhigh/curve/points", "type": "floatvarar", "readonly": false, "cmd": "tt Tmxhigh/curve/points", "visibility": 3}, +{"path": "Tmxhigh/alarm", "type": "float", "readonly": false, "cmd": "tt Tmxhigh/alarm"}, +{"path": "Tmxhigh/raw", "type": "float"}, +{"path": "Tmxcx", "type": "float", "kids": 14}, +{"path": "Tmxcx/active", "type": "enum", "enum": {"inactive": 0, "active": 1}, "readonly": false, "cmd": "tt Tmxcx/active"}, +{"path": "Tmxcx/autorange", "type": "bool", "readonly": false, "cmd": "tt Tmxcx/autorange", "description": "autorange (common for all channels)"}, +{"path": "Tmxcx/range", "type": "text", "readonly": false, "cmd": "tt Tmxcx/range", "description": "resistance range in Ohm"}, +{"path": "Tmxcx/range_num", "type": "int"}, +{"path": "Tmxcx/excitation", "type": "text", "readonly": false, "cmd": "tt Tmxcx/excitation", "description": "excitation with unit, i.e. 2uV or 3pA"}, +{"path": "Tmxcx/excitation_num", "type": "int"}, +{"path": "Tmxcx/excitation_mode", "type": "enum", "enum": {"voltage": 0, "current": 1, "off": 2}}, +{"path": "Tmxcx/pause", "type": "int", "readonly": false, "cmd": "tt Tmxcx/pause", "description": "pause time [sec] after channel change"}, +{"path": "Tmxcx/filter", "type": "int", "readonly": false, "cmd": "tt Tmxcx/filter", "description": "filter average time [sec]"}, +{"path": "Tmxcx/dwell", "type": "int", "readonly": false, "cmd": "tt Tmxcx/dwell", "description": "dwell time [sec]. Total time per channel: pause + filter + dwell"}, +{"path": "Tmxcx/status", "type": "text"}, +{"path": "Tmxcx/curve", "type": "text", "readonly": false, "cmd": "tt Tmxcx/curve", "kids": 1}, +{"path": "Tmxcx/curve/points", "type": "floatvarar", "readonly": false, "cmd": "tt Tmxcx/curve/points", "visibility": 3}, +{"path": "Tmxcx/alarm", "type": "float", "readonly": false, "cmd": "tt Tmxcx/alarm"}, +{"path": "Tmxcx/raw", "type": "float"}, +{"path": "Tblueo", "type": "float", "kids": 14}, +{"path": "Tblueo/active", "type": "enum", "enum": {"inactive": 0, "active": 1}, "readonly": false, "cmd": "tt Tblueo/active"}, +{"path": "Tblueo/autorange", "type": "bool", "readonly": false, "cmd": "tt Tblueo/autorange", "description": "autorange (common for all channels)"}, +{"path": "Tblueo/range", "type": "text", "readonly": false, "cmd": "tt Tblueo/range", "description": "resistance range in Ohm"}, +{"path": "Tblueo/range_num", "type": "int"}, +{"path": "Tblueo/excitation", "type": "text", "readonly": false, "cmd": "tt Tblueo/excitation", "description": "excitation with unit, i.e. 2uV or 3pA"}, +{"path": "Tblueo/excitation_num", "type": "int"}, +{"path": "Tblueo/excitation_mode", "type": "enum", "enum": {"voltage": 0, "current": 1, "off": 2}}, +{"path": "Tblueo/pause", "type": "int", "readonly": false, "cmd": "tt Tblueo/pause", "description": "pause time [sec] after channel change"}, +{"path": "Tblueo/filter", "type": "int", "readonly": false, "cmd": "tt Tblueo/filter", "description": "filter average time [sec]"}, +{"path": "Tblueo/dwell", "type": "int", "readonly": false, "cmd": "tt Tblueo/dwell", "description": "dwell time [sec]. Total time per channel: pause + filter + dwell"}, +{"path": "Tblueo/status", "type": "text"}, +{"path": "Tblueo/curve", "type": "text", "readonly": false, "cmd": "tt Tblueo/curve", "kids": 1}, +{"path": "Tblueo/curve/points", "type": "floatvarar", "readonly": false, "cmd": "tt Tblueo/curve/points", "visibility": 3}, +{"path": "Tblueo/alarm", "type": "float", "readonly": false, "cmd": "tt Tblueo/alarm"}, +{"path": "Tblueo/raw", "type": "float"}, +{"path": "Tpt50", "type": "float", "kids": 14}, +{"path": "Tpt50/active", "type": "enum", "enum": {"inactive": 0, "active": 1}, "readonly": false, "cmd": "tt Tpt50/active"}, +{"path": "Tpt50/autorange", "type": "bool", "readonly": false, "cmd": "tt Tpt50/autorange", "description": "autorange (common for all channels)"}, +{"path": "Tpt50/range", "type": "text", "readonly": false, "cmd": "tt Tpt50/range", "description": "resistance range in Ohm"}, +{"path": "Tpt50/range_num", "type": "int"}, +{"path": "Tpt50/excitation", "type": "text", "readonly": false, "cmd": "tt Tpt50/excitation", "description": "excitation with unit, i.e. 2uV or 3pA"}, +{"path": "Tpt50/excitation_num", "type": "int"}, +{"path": "Tpt50/excitation_mode", "type": "enum", "enum": {"voltage": 0, "current": 1, "off": 2}}, +{"path": "Tpt50/pause", "type": "int", "readonly": false, "cmd": "tt Tpt50/pause", "description": "pause time [sec] after channel change"}, +{"path": "Tpt50/filter", "type": "int", "readonly": false, "cmd": "tt Tpt50/filter", "description": "filter average time [sec]"}, +{"path": "Tpt50/dwell", "type": "int", "readonly": false, "cmd": "tt Tpt50/dwell", "description": "dwell time [sec]. Total time per channel: pause + filter + dwell"}, +{"path": "Tpt50/status", "type": "text"}, +{"path": "Tpt50/curve", "type": "text", "readonly": false, "cmd": "tt Tpt50/curve", "kids": 1}, +{"path": "Tpt50/curve/points", "type": "floatvarar", "readonly": false, "cmd": "tt Tpt50/curve/points", "visibility": 3}, +{"path": "Tpt50/alarm", "type": "float", "readonly": false, "cmd": "tt Tpt50/alarm"}, +{"path": "Tpt50/raw", "type": "float"}, +{"path": "Tpt3high", "type": "float", "kids": 14}, +{"path": "Tpt3high/active", "type": "enum", "enum": {"inactive": 0, "active": 1}, "readonly": false, "cmd": "tt Tpt3high/active"}, +{"path": "Tpt3high/autorange", "type": "bool", "readonly": false, "cmd": "tt Tpt3high/autorange", "description": "autorange (common for all channels)"}, +{"path": "Tpt3high/range", "type": "text", "readonly": false, "cmd": "tt Tpt3high/range", "description": "resistance range in Ohm"}, +{"path": "Tpt3high/range_num", "type": "int"}, +{"path": "Tpt3high/excitation", "type": "text", "readonly": false, "cmd": "tt Tpt3high/excitation", "description": "excitation with unit, i.e. 2uV or 3pA"}, +{"path": "Tpt3high/excitation_num", "type": "int"}, +{"path": "Tpt3high/excitation_mode", "type": "enum", "enum": {"voltage": 0, "current": 1, "off": 2}}, +{"path": "Tpt3high/pause", "type": "int", "readonly": false, "cmd": "tt Tpt3high/pause", "description": "pause time [sec] after channel change"}, +{"path": "Tpt3high/filter", "type": "int", "readonly": false, "cmd": "tt Tpt3high/filter", "description": "filter average time [sec]"}, +{"path": "Tpt3high/dwell", "type": "int", "readonly": false, "cmd": "tt Tpt3high/dwell", "description": "dwell time [sec]. Total time per channel: pause + filter + dwell"}, +{"path": "Tpt3high/status", "type": "text"}, +{"path": "Tpt3high/curve", "type": "text", "readonly": false, "cmd": "tt Tpt3high/curve", "kids": 1}, +{"path": "Tpt3high/curve/points", "type": "floatvarar", "readonly": false, "cmd": "tt Tpt3high/curve/points", "visibility": 3}, +{"path": "Tpt3high/alarm", "type": "float", "readonly": false, "cmd": "tt Tpt3high/alarm"}, +{"path": "Tpt3high/raw", "type": "float"}, +{"path": "Tpt3low", "type": "float", "kids": 14}, +{"path": "Tpt3low/active", "type": "enum", "enum": {"inactive": 0, "active": 1}, "readonly": false, "cmd": "tt Tpt3low/active"}, +{"path": "Tpt3low/autorange", "type": "bool", "readonly": false, "cmd": "tt Tpt3low/autorange", "description": "autorange (common for all channels)"}, +{"path": "Tpt3low/range", "type": "text", "readonly": false, "cmd": "tt Tpt3low/range", "description": "resistance range in Ohm"}, +{"path": "Tpt3low/range_num", "type": "int"}, +{"path": "Tpt3low/excitation", "type": "text", "readonly": false, "cmd": "tt Tpt3low/excitation", "description": "excitation with unit, i.e. 2uV or 3pA"}, +{"path": "Tpt3low/excitation_num", "type": "int"}, +{"path": "Tpt3low/excitation_mode", "type": "enum", "enum": {"voltage": 0, "current": 1, "off": 2}}, +{"path": "Tpt3low/pause", "type": "int", "readonly": false, "cmd": "tt Tpt3low/pause", "description": "pause time [sec] after channel change"}, +{"path": "Tpt3low/filter", "type": "int", "readonly": false, "cmd": "tt Tpt3low/filter", "description": "filter average time [sec]"}, +{"path": "Tpt3low/dwell", "type": "int", "readonly": false, "cmd": "tt Tpt3low/dwell", "description": "dwell time [sec]. Total time per channel: pause + filter + dwell"}, +{"path": "Tpt3low/status", "type": "text"}, +{"path": "Tpt3low/curve", "type": "text", "readonly": false, "cmd": "tt Tpt3low/curve", "kids": 1}, +{"path": "Tpt3low/curve/points", "type": "floatvarar", "readonly": false, "cmd": "tt Tpt3low/curve/points", "visibility": 3}, +{"path": "Tpt3low/alarm", "type": "float", "readonly": false, "cmd": "tt Tpt3low/alarm"}, +{"path": "Tpt3low/raw", "type": "float"}, +{"path": "Twhite", "type": "float", "kids": 14}, +{"path": "Twhite/active", "type": "enum", "enum": {"inactive": 0, "active": 1}, "readonly": false, "cmd": "tt Twhite/active"}, +{"path": "Twhite/autorange", "type": "bool", "readonly": false, "cmd": "tt Twhite/autorange", "description": "autorange (common for all channels)"}, +{"path": "Twhite/range", "type": "text", "readonly": false, "cmd": "tt Twhite/range", "description": "resistance range in Ohm"}, +{"path": "Twhite/range_num", "type": "int"}, +{"path": "Twhite/excitation", "type": "text", "readonly": false, "cmd": "tt Twhite/excitation", "description": "excitation with unit, i.e. 2uV or 3pA"}, +{"path": "Twhite/excitation_num", "type": "int"}, +{"path": "Twhite/excitation_mode", "type": "enum", "enum": {"voltage": 0, "current": 1, "off": 2}}, +{"path": "Twhite/pause", "type": "int", "readonly": false, "cmd": "tt Twhite/pause", "description": "pause time [sec] after channel change"}, +{"path": "Twhite/filter", "type": "int", "readonly": false, "cmd": "tt Twhite/filter", "description": "filter average time [sec]"}, +{"path": "Twhite/dwell", "type": "int", "readonly": false, "cmd": "tt Twhite/dwell", "description": "dwell time [sec]. Total time per channel: pause + filter + dwell"}, +{"path": "Twhite/status", "type": "text"}, +{"path": "Twhite/curve", "type": "text", "readonly": false, "cmd": "tt Twhite/curve", "kids": 1}, +{"path": "Twhite/curve/points", "type": "floatvarar", "readonly": false, "cmd": "tt Twhite/curve/points", "visibility": 3}, +{"path": "Twhite/alarm", "type": "float", "readonly": false, "cmd": "tt Twhite/alarm"}, +{"path": "Twhite/raw", "type": "float"}, +{"path": "Tgreen", "type": "float", "kids": 14}, +{"path": "Tgreen/active", "type": "enum", "enum": {"inactive": 0, "active": 1}, "readonly": false, "cmd": "tt Tgreen/active"}, +{"path": "Tgreen/autorange", "type": "bool", "readonly": false, "cmd": "tt Tgreen/autorange", "description": "autorange (common for all channels)"}, +{"path": "Tgreen/range", "type": "text", "readonly": false, "cmd": "tt Tgreen/range", "description": "resistance range in Ohm"}, +{"path": "Tgreen/range_num", "type": "int"}, +{"path": "Tgreen/excitation", "type": "text", "readonly": false, "cmd": "tt Tgreen/excitation", "description": "excitation with unit, i.e. 2uV or 3pA"}, +{"path": "Tgreen/excitation_num", "type": "int"}, +{"path": "Tgreen/excitation_mode", "type": "enum", "enum": {"voltage": 0, "current": 1, "off": 2}}, +{"path": "Tgreen/pause", "type": "int", "readonly": false, "cmd": "tt Tgreen/pause", "description": "pause time [sec] after channel change"}, +{"path": "Tgreen/filter", "type": "int", "readonly": false, "cmd": "tt Tgreen/filter", "description": "filter average time [sec]"}, +{"path": "Tgreen/dwell", "type": "int", "readonly": false, "cmd": "tt Tgreen/dwell", "description": "dwell time [sec]. Total time per channel: pause + filter + dwell"}, +{"path": "Tgreen/status", "type": "text"}, +{"path": "Tgreen/curve", "type": "text", "readonly": false, "cmd": "tt Tgreen/curve", "kids": 1}, +{"path": "Tgreen/curve/points", "type": "floatvarar", "readonly": false, "cmd": "tt Tgreen/curve/points", "visibility": 3}, +{"path": "Tgreen/alarm", "type": "float", "readonly": false, "cmd": "tt Tgreen/alarm"}, +{"path": "Tgreen/raw", "type": "float"}, +{"path": "analog2", "type": "float", "readonly": false, "cmd": "tt analog2"}, +{"path": "remote", "type": "bool"}, +{"path": "display", "type": "text", "readonly": false, "cmd": "tt display"}]}, + +"cmn": {"base": "/cmn", "params": [ +{"path": "", "type": "none", "kids": 6}, +{"path": "send", "type": "text", "readonly": false, "cmd": "cmn send", "visibility": 3}, +{"path": "status", "type": "text", "visibility": 3}, +{"path": "u1", "type": "float"}, +{"path": "temp", "type": "float"}, +{"path": "u2", "type": "float"}, +{"path": "chan", "type": "enum", "enum": {"auto": 0, "chan1": 1, "chan2": 2}, "readonly": false, "cmd": "cmn chan"}]}} diff --git a/frappy_psi/ls370res.py b/frappy_psi/ls370res.py index 89274a0a..994ecc60 100644 --- a/frappy_psi/ls370res.py +++ b/frappy_psi/ls370res.py @@ -34,7 +34,7 @@ from frappy.lib import formatStatusBits from frappy.core import Done, Drivable, Parameter, Property, CommonReadHandler, CommonWriteHandler from frappy.io import HasIO from frappy_psi.channelswitcher import Channel, ChannelSwitcher -from frappy_psi.picontrol import HasConvergence +from frappy.ctrlby import WrapControlledBy Status = Drivable.Status diff --git a/frappy_psi/picontrol.py b/frappy_psi/picontrol.py index 6c9316e1..d1b14f89 100644 --- a/frappy_psi/picontrol.py +++ b/frappy_psi/picontrol.py @@ -36,8 +36,8 @@ and this is an example cfg 'controlled T', meaning=['temperature', 20], output_module='htr_sample', - p=1, - i=0.01, + p=100, + i=60, ) @@ -60,79 +60,164 @@ example cfg: import time import math +import numpy as np from frappy.core import Readable, Writable, Parameter, Attached, IDLE, Property from frappy.lib import clamp, merge_status from frappy.datatypes import LimitsType, EnumType, FloatRange +from frappy.errors import SECoPError from frappy.ctrlby import HasOutputModule, WrapControlledBy from frappy_psi.convergence import HasConvergence +def ext_poll_value(mobj): + prev = mobj.parameters['value'].timestamp + mobj.doPoll() + if mobj.parameters['value'].timestamp <= prev: + # value was not updated + mobj.read_value() + # disable polling for the next interval + interval = mobj.pollInfo.interval + if interval: + mobj.pollInfo.last_main = (time.time() // interval) * interval + return mobj.value + + class PImixin(HasOutputModule, Writable): - p = Parameter('proportional term', FloatRange(0), readonly=False) - i = Parameter('integral term', FloatRange(0), readonly=False) + value = Parameter(unit='K', update_unchanged='always') + p = Parameter('proportional term', FloatRange(0), readonly=False, default=1) + i = Parameter('integral term', FloatRange(0), readonly=False, default=1) + status = Parameter(update_unchanged='never') + itime = Parameter('integration time', FloatRange(0, unit='s'), default=60, readonly=False) + control_active = Parameter(readonly=False) + # output_module is inherited - output_range = Property('legacy output range', LimitsType(FloatRange()), default=(0,0)) + output_range = Property('legacy output range', LimitsType(FloatRange()), default=(0, 0)) output_min = Parameter('min output', FloatRange(), default=0, readonly=False) output_max = Parameter('max output', FloatRange(), default=0, readonly=False) - output_func = Parameter('output function', - EnumType(lin=0, square=1), readonly=False, value=0) - value = Parameter(unit='K') + input_scale = Property('input scale', FloatRange(unit='$'), default=100) + time_scale = Property('time scale', FloatRange(unit='s'), default=60) + overflow = Parameter('overflow', FloatRange(), default=0, readonly=False) + _lastdiff = None _lasttime = 0 _get_range = None # a function get output range from output_module _overflow = 0 - _cvt2int = None - _cvt2ext = None + _itime_set = None # True: 'itime' was set, False: 'i' was set + _history = None + __errcnt = 0 + __inside_poll = False + __cache = None + + # with input units K and output units %: + # units for p: % / K + # units for i: % / K / min def initModule(self): + self.__cache = {} super().initModule() if self.output_range != (0, 0): # legacy ! self.output_min, self.output_max = self.output_range + self.get_range_func() + self.addCallback('value', self.__inside, 'value') + self.addCallback('status', self.__inside, 'status') + + def __inside(self, value, pname): + if self.__inside_poll is not None: + self.__cache[pname] = value def doPoll(self): - super().doPoll() - if not self.control_active: - return - out = self.output_module - now = time.time() - deltat = clamp(0, now-self._lasttime, 10) - self._lasttime = now - diff = self.target - self.value - if self._lastdiff is None: + try: + self.__inside_poll = True + self.__cache = {} + now = time.time() + value = self.read_value() + if self._history is None: + # initialize a fixed size array, with fake time axis to avoid errors in np.polyfit + self._history = np.array([(now+i, self.value) for i in range(-9,1)]) + else: + # shift fixed size array, and change last point + self._history[:-1] = self._history[1:] + self._history[-1] = (now, value) + if not self.control_active: + self._lastdiff = 0 + return + self.read_status() + out = self.output_module + deltat = clamp(0, now-self._lasttime, 10) + self._lasttime = now + diff = self.target - value + if self._lastdiff is None: + self._lastdiff = diff + deltadiff = diff - self._lastdiff self._lastdiff = diff - deltadiff = diff - self._lastdiff - self._lastdiff = diff - output, omin, omax = self._cvt2int(out.target) - output += self._overflow + self.p * deltadiff + self.i * deltat * diff - if output < omin: - self._overflow = max(omin - omax, output - omin) - output = omin - elif output > omax: - self._overflow = min(omax - omin, output - omax) - output = omax - else: - self._overflow = 0 - out.update_target(self.name, self._cvt2ext(output)) + if diff: + ref = self.itime / diff + (slope, _), cov = np.polyfit(self._history[:, 0] - now, self._history[:, 1], 1, cov=True) + slope_stddev = np.sqrt(max(0, cov[0, 0])) + if slope * ref > 1 + 2 * slope_stddev * abs(ref): + # extrapolated value will cross target in less than itime + if self._overflow: + self._overflow = 0 + self.log.info('clear overflow') + + output, omin, omax = self.cvt2int(out.target) + output += self._overflow + ( + self.p * deltadiff + + self.i * deltat * diff / self.time_scale) / self.input_scale + if omin <= output <= omax: + self._overflow = 0 + else: + # save overflow for next step + if output < omin: + self._overflow = output - omin + output = omin + else: + self._overflow = output - omax + output = omax + out.update_target(self.name, self.cvt2ext(output)) + self.__errcnt = 0 + except Exception as e: + if self.control_active: + self.__errcnt += 1 + if self.__errcnt > 5: + self.__errcnt = 0 + self.log.warning('too many errors - switch control off') + self.write_control_active(False) + raise + finally: + self.__inside_poll = False + self.__cache = {} + self.overflow = self._overflow + + def write_overflow(self, value): + self._overflow = value + + def internal_poll(self): + super().doPoll() + + def internal_read_value(self): + return super().read_value() + + def internal_read_status(self): + return super().read_status() + + def read_value(self): + try: + return self.__cache['value'] + except KeyError: + return self.internal_read_value() def read_status(self): + try: + return self.__cache['status'] + except KeyError: + pass status = IDLE, 'controlling' if self.control_active else 'inactive' if hasattr(super(), 'read_status'): - status = merge_status(super().read_status(), status) + status = merge_status(self.internal_read_status(), status) return status - def cvt2int_square(self, output): - return (math.sqrt(max(0, clamp(x, *self._get_range()))) for x in (output, self.output_min, self.output_max)) - - def cvt2ext_square(self, output): - return output ** 2 - - def cvt2int_lin(self, output): - return (clamp(x, *self._get_range()) for x in (output, self.output_min, self.output_max)) - - def cvt2ext_lin(self, output): - return output - - def write_output_func(self, value): + def get_range_func(self): out = self.output_module if hasattr(out, 'max_target'): if hasattr(self, 'min_target'): @@ -147,32 +232,72 @@ class PImixin(HasOutputModule, Writable): self._get_range = lambda o=self: (o.output_min, o.output_max) if self.output_min == self.output_max == 0: self.output_min, self.output_max = self._get_range() - self.output_func = value - self._cvt2int = getattr(self, f'cvt2int_{self.output_func.name}') - self._cvt2ext = getattr(self, f'cvt2ext_{self.output_func.name}') - # not needed, done by HasOutputModule.write_control_active - # def write_control_active(self, value): - # super().write_control_active(value) - # out = self.output_module - # if not value: - # out.write_target(out.parameters['target'].datatype.default) + def cvt2int(self, output): + return (clamp(x, *self._get_range()) for x in (output, self.output_min, self.output_max)) + + def cvt2ext(self, output): + return output + + def calc_itime(self, prop, integ): + return prop * self.time_scale / integ + + def write_p(self, value): + if self._itime_set: + self.i = value * self.time_scale / self.itime + elif self._itime_set is False: # means also not None + self.itime = value * self.time_scale / self.i + + def write_i(self, value): + self._itime_set = False + self.itime = self.p * self.time_scale / value + + def write_itime(self, value): + self._itime_set = True + self.i = self.p * self.time_scale / value def set_target(self, value): if not self.control_active: self.activate_control() + self.target = value + self.doPoll() + + +class PImixinSquare(PImixin): + """unchecked: use square as output function""" + def cvt2int(self, output): + return (math.sqrt(max(0, 100 * clamp(x, *self._get_range()))) + for x in (output, self.output_min, self.output_max)) + + def cvt2ext(self, output): + return output ** 2 / 100 class PI(HasConvergence, PImixin): input_module = Attached(Readable, 'the input module') - def read_value(self): - return self.input_module.value + def internal_poll(self): + inp = self.input_module + inp.doPoll() + interval = inp.pollInfo.interval + if interval > 0: + # disable next internal poll + inp.pollInfo.last_main = (time.time() // interval) * interval + self.read_value() + self.read_status() - def read_status(self): - return self.input_module.status + def internal_read_value(self): + return self.input_module.read_value() + + def internal_read_status(self): + return self.input_module.read_status() + + def write_target(self, target): + super().write_target(target) + self.convergence_start() +# unchecked! class PI2(PI): maxovershoot = Parameter('max. overshoot', FloatRange(0, 100, unit='%'), readonly=False, default=20) diff --git a/frappy_psi/tcs.py b/frappy_psi/tcs.py index 4180146b..1aa1e05a 100644 --- a/frappy_psi/tcs.py +++ b/frappy_psi/tcs.py @@ -21,6 +21,7 @@ from frappy.core import StringIO, HasIO, Writable, Parameter, Property, FloatRange, IntRange, BoolType, \ ERROR from frappy.errors import CommunicationFailedError, HardwareError +from frappy.ctrlby import WrapControlledBy class IO(StringIO): @@ -36,7 +37,7 @@ class Heater(HasIO, Writable): channel = Property('channel (source number)', IntRange(1, 3)) value = Parameter('current reading', FloatRange(0, 0.1, unit='A')) target = Parameter('current target value', FloatRange(0, 0.1, unit='A'), readonly=False) - on = Parameter('turn current on/off', BoolType(), readonly=False) + on = Parameter('turn current on/off', BoolType(), readonly=False, default=False) def query_status(self): reply, txtvalue = self.communicate('STATUS?').split('\t') @@ -64,13 +65,14 @@ class Heater(HasIO, Writable): if reply != '0': # not as in manual raise CommunicationFailedError(f'Bad reply: {reply!r}') - def read_target(self): + def read_value(self): txtvalue = self.query_status() current_range = txtvalue[(self.channel - 1) * 4 + 1] current = txtvalue[(self.channel - 1) * 4 + 1 + 1] # percent of range - multipliers = {'1': 99e-6, '2': 990e-6, '3': 9900e-6, '4': 99e-3} + multipliers = {'1': 1e-4, '2': 1e-3, '3': 1e-2, '4': 1e-1} value = float(current) / 100 * float(multipliers[current_range]) return value - # no measured value available - read_value = read_target + +class WrappedHeater(WrapControlledBy, Heater): + pass