Merge branch 'wip' of gitlab.psi.ch-samenv:samenv/frappy into wip
This commit is contained in:
commit
971c1dcfee
@ -12,6 +12,7 @@ service = main
|
|||||||
class = secop_psi.sea.SeaDrivable
|
class = secop_psi.sea.SeaDrivable
|
||||||
io = sea_main
|
io = sea_main
|
||||||
sea_object = tt
|
sea_object = tt
|
||||||
|
rel_paths = . tt
|
||||||
|
|
||||||
[T_ccr]
|
[T_ccr]
|
||||||
class = secop_psi.sea.SeaReadable
|
class = secop_psi.sea.SeaReadable
|
||||||
|
@ -63,5 +63,5 @@ uri = ma6-ts.psi.ch:3003
|
|||||||
description = stick rotation, typically used for omega
|
description = stick rotation, typically used for omega
|
||||||
class = secop_psi.phytron.Motor
|
class = secop_psi.phytron.Motor
|
||||||
io = om_io
|
io = om_io
|
||||||
encoder_mode = CHECK
|
encoder_mode = NO
|
||||||
|
|
||||||
|
@ -1,50 +1,50 @@
|
|||||||
{"tt": {"base": "/tt", "params": [
|
{"tt": {"base": "/tt", "params": [
|
||||||
{"path": "", "type": "float", "readonly": false, "cmd": "run tt", "description": "tt", "kids": 18},
|
{"path": "", "type": "float", "readonly": false, "cmd": "run tt", "description": "tt", "kids": 18},
|
||||||
{"path": "send", "type": "text", "readonly": false, "cmd": "tt send", "visibility": 3},
|
{"path": "send", "type": "text", "readonly": false, "cmd": "tt send", "visibility": 3},
|
||||||
{"path": "status", "type": "text", "visibility": 3},
|
{"path": "status", "type": "text", "readonly": false, "cmd": "run tt", "visibility": 3},
|
||||||
{"path": "is_running", "type": "int", "readonly": false, "cmd": "tt is_running", "visibility": 3},
|
{"path": "is_running", "type": "int", "readonly": false, "cmd": "tt is_running", "visibility": 3},
|
||||||
{"path": "mainloop", "type": "text", "readonly": false, "cmd": "tt mainloop", "visibility": 3},
|
{"path": "mainloop", "type": "text", "readonly": false, "cmd": "tt mainloop", "visibility": 3},
|
||||||
{"path": "target", "type": "float"},
|
{"path": "target", "type": "float", "readonly": false, "cmd": "run tt"},
|
||||||
{"path": "running", "type": "int"},
|
{"path": "running", "type": "int", "readonly": false, "cmd": "run tt"},
|
||||||
{"path": "tolerance", "type": "float", "readonly": false, "cmd": "tt tolerance"},
|
{"path": "tolerance", "type": "float", "readonly": false, "cmd": "tt tolerance"},
|
||||||
{"path": "maxwait", "type": "float", "readonly": false, "cmd": "tt maxwait"},
|
{"path": "maxwait", "type": "float", "readonly": false, "cmd": "tt maxwait"},
|
||||||
{"path": "settle", "type": "float", "readonly": false, "cmd": "tt settle"},
|
{"path": "settle", "type": "float", "readonly": false, "cmd": "tt settle"},
|
||||||
{"path": "log", "type": "text", "readonly": false, "cmd": "tt log", "visibility": 3, "kids": 4},
|
{"path": "log", "type": "text", "readonly": false, "cmd": "tt log", "visibility": 3, "kids": 4},
|
||||||
{"path": "log/mean", "type": "float", "visibility": 3},
|
{"path": "log/mean", "type": "float", "readonly": false, "cmd": "run tt", "visibility": 3},
|
||||||
{"path": "log/m2", "type": "float", "visibility": 3},
|
{"path": "log/m2", "type": "float", "readonly": false, "cmd": "run tt", "visibility": 3},
|
||||||
{"path": "log/stddev", "type": "float", "visibility": 3},
|
{"path": "log/stddev", "type": "float", "readonly": false, "cmd": "run tt", "visibility": 3},
|
||||||
{"path": "log/n", "type": "float", "visibility": 3},
|
{"path": "log/n", "type": "float", "readonly": false, "cmd": "run tt", "visibility": 3},
|
||||||
{"path": "dblctrl", "type": "bool", "readonly": false, "cmd": "tt dblctrl", "kids": 9},
|
{"path": "dblctrl", "type": "bool", "readonly": false, "cmd": "tt dblctrl", "kids": 9},
|
||||||
{"path": "dblctrl/tshift", "type": "float", "readonly": false, "cmd": "tt dblctrl/tshift"},
|
{"path": "dblctrl/tshift", "type": "float", "readonly": false, "cmd": "tt dblctrl/tshift"},
|
||||||
{"path": "dblctrl/mode", "type": "enum", "enum": {"disabled": -1, "inactive": 0, "stable": 1, "up": 2, "down": 3}, "readonly": false, "cmd": "tt dblctrl/mode"},
|
{"path": "dblctrl/mode", "type": "enum", "enum": {"disabled": -1, "inactive": 0, "stable": 1, "up": 2, "down": 3}, "readonly": false, "cmd": "tt dblctrl/mode"},
|
||||||
{"path": "dblctrl/shift_up", "type": "float"},
|
{"path": "dblctrl/shift_up", "type": "float", "readonly": false, "cmd": "run tt"},
|
||||||
{"path": "dblctrl/shift_lo", "type": "float"},
|
{"path": "dblctrl/shift_lo", "type": "float", "readonly": false, "cmd": "run tt"},
|
||||||
{"path": "dblctrl/t_min", "type": "float"},
|
{"path": "dblctrl/t_min", "type": "float", "readonly": false, "cmd": "run tt"},
|
||||||
{"path": "dblctrl/t_max", "type": "float"},
|
{"path": "dblctrl/t_max", "type": "float", "readonly": false, "cmd": "run tt"},
|
||||||
{"path": "dblctrl/int2", "type": "float", "readonly": false, "cmd": "tt dblctrl/int2"},
|
{"path": "dblctrl/int2", "type": "float", "readonly": false, "cmd": "tt dblctrl/int2"},
|
||||||
{"path": "dblctrl/prop_up", "type": "float", "readonly": false, "cmd": "tt dblctrl/prop_up"},
|
{"path": "dblctrl/prop_up", "type": "float", "readonly": false, "cmd": "tt dblctrl/prop_up"},
|
||||||
{"path": "dblctrl/prop_lo", "type": "float", "readonly": false, "cmd": "tt dblctrl/prop_lo"},
|
{"path": "dblctrl/prop_lo", "type": "float", "readonly": false, "cmd": "tt dblctrl/prop_lo"},
|
||||||
{"path": "tm", "type": "float", "kids": 4},
|
{"path": "tm", "type": "float", "readonly": false, "cmd": "run tt", "kids": 4},
|
||||||
{"path": "tm/curve", "type": "text", "readonly": false, "cmd": "tt tm/curve", "kids": 1},
|
{"path": "tm/curve", "type": "text", "readonly": false, "cmd": "tt tm/curve", "kids": 1},
|
||||||
{"path": "tm/curve/points", "type": "floatvarar", "readonly": false, "cmd": "tt tm/curve/points", "visibility": 3},
|
{"path": "tm/curve/points", "type": "floatvarar", "readonly": false, "cmd": "tt tm/curve/points", "visibility": 3},
|
||||||
{"path": "tm/alarm", "type": "float", "readonly": false, "cmd": "tt tm/alarm"},
|
{"path": "tm/alarm", "type": "float", "readonly": false, "cmd": "tt tm/alarm"},
|
||||||
{"path": "tm/stddev", "type": "float"},
|
{"path": "tm/stddev", "type": "float", "readonly": false, "cmd": "run tt"},
|
||||||
{"path": "tm/raw", "type": "float"},
|
{"path": "tm/raw", "type": "float", "readonly": false, "cmd": "run tt"},
|
||||||
{"path": "ts", "type": "float", "kids": 4},
|
{"path": "ts", "type": "float", "readonly": false, "cmd": "run tt", "kids": 4},
|
||||||
{"path": "ts/curve", "type": "text", "readonly": false, "cmd": "tt ts/curve", "kids": 1},
|
{"path": "ts/curve", "type": "text", "readonly": false, "cmd": "tt ts/curve", "kids": 1},
|
||||||
{"path": "ts/curve/points", "type": "floatvarar", "readonly": false, "cmd": "tt ts/curve/points", "visibility": 3},
|
{"path": "ts/curve/points", "type": "floatvarar", "readonly": false, "cmd": "tt ts/curve/points", "visibility": 3},
|
||||||
{"path": "ts/alarm", "type": "float", "readonly": false, "cmd": "tt ts/alarm"},
|
{"path": "ts/alarm", "type": "float", "readonly": false, "cmd": "tt ts/alarm"},
|
||||||
{"path": "ts/stddev", "type": "float"},
|
{"path": "ts/stddev", "type": "float", "readonly": false, "cmd": "run tt"},
|
||||||
{"path": "ts/raw", "type": "float"},
|
{"path": "ts/raw", "type": "float", "readonly": false, "cmd": "run tt"},
|
||||||
{"path": "ts_2", "type": "float", "kids": 4},
|
{"path": "ts_2", "type": "float", "readonly": false, "cmd": "run tt", "kids": 4},
|
||||||
{"path": "ts_2/curve", "type": "text", "readonly": false, "cmd": "tt ts_2/curve", "kids": 1},
|
{"path": "ts_2/curve", "type": "text", "readonly": false, "cmd": "tt ts_2/curve", "kids": 1},
|
||||||
{"path": "ts_2/curve/points", "type": "floatvarar", "readonly": false, "cmd": "tt ts_2/curve/points", "visibility": 3},
|
{"path": "ts_2/curve/points", "type": "floatvarar", "readonly": false, "cmd": "tt ts_2/curve/points", "visibility": 3},
|
||||||
{"path": "ts_2/alarm", "type": "float", "readonly": false, "cmd": "tt ts_2/alarm"},
|
{"path": "ts_2/alarm", "type": "float", "readonly": false, "cmd": "tt ts_2/alarm"},
|
||||||
{"path": "ts_2/stddev", "type": "float"},
|
{"path": "ts_2/stddev", "type": "float", "readonly": false, "cmd": "run tt"},
|
||||||
{"path": "ts_2/raw", "type": "float"},
|
{"path": "ts_2/raw", "type": "float", "readonly": false, "cmd": "run tt"},
|
||||||
{"path": "set", "type": "float", "readonly": false, "cmd": "tt set", "kids": 18},
|
{"path": "set", "type": "float", "readonly": false, "cmd": "tt set", "kids": 18},
|
||||||
{"path": "set/mode", "type": "enum", "enum": {"disabled": -1, "off": 0, "controlling": 1, "manual": 2}, "readonly": false, "cmd": "tt set/mode"},
|
{"path": "set/mode", "type": "enum", "enum": {"disabled": -1, "off": 0, "controlling": 1, "manual": 2}, "readonly": false, "cmd": "tt set/mode"},
|
||||||
{"path": "set/reg", "type": "float"},
|
{"path": "set/reg", "type": "float", "readonly": false, "cmd": "run tt"},
|
||||||
{"path": "set/ramp", "type": "float", "readonly": false, "cmd": "tt set/ramp", "description": "maximum ramp in K/min (0: ramp off)"},
|
{"path": "set/ramp", "type": "float", "readonly": false, "cmd": "tt set/ramp", "description": "maximum ramp in K/min (0: ramp off)"},
|
||||||
{"path": "set/wramp", "type": "float", "readonly": false, "cmd": "tt set/wramp"},
|
{"path": "set/wramp", "type": "float", "readonly": false, "cmd": "tt set/wramp"},
|
||||||
{"path": "set/smooth", "type": "float", "readonly": false, "cmd": "tt set/smooth", "description": "smooth time (minutes)"},
|
{"path": "set/smooth", "type": "float", "readonly": false, "cmd": "tt set/smooth", "description": "smooth time (minutes)"},
|
||||||
@ -53,17 +53,17 @@
|
|||||||
{"path": "set/resist", "type": "float", "readonly": false, "cmd": "tt set/resist"},
|
{"path": "set/resist", "type": "float", "readonly": false, "cmd": "tt set/resist"},
|
||||||
{"path": "set/maxheater", "type": "text", "readonly": false, "cmd": "tt set/maxheater", "description": "maximum heater limit, units should be given without space: W, mW, A, mA"},
|
{"path": "set/maxheater", "type": "text", "readonly": false, "cmd": "tt set/maxheater", "description": "maximum heater limit, units should be given without space: W, mW, A, mA"},
|
||||||
{"path": "set/linearpower", "type": "float", "readonly": false, "cmd": "tt set/linearpower", "description": "when not 0, it is the maximum effective power, and the power is linear to the heater output"},
|
{"path": "set/linearpower", "type": "float", "readonly": false, "cmd": "tt set/linearpower", "description": "when not 0, it is the maximum effective power, and the power is linear to the heater output"},
|
||||||
{"path": "set/maxpowerlim", "type": "float", "description": "the maximum power limit (before any booster or converter)"},
|
{"path": "set/maxpowerlim", "type": "float", "readonly": false, "cmd": "run tt", "description": "the maximum power limit (before any booster or converter)"},
|
||||||
{"path": "set/maxpower", "type": "float", "readonly": false, "cmd": "tt set/maxpower", "description": "maximum power [W]"},
|
{"path": "set/maxpower", "type": "float", "readonly": false, "cmd": "tt set/maxpower", "description": "maximum power [W]"},
|
||||||
{"path": "set/maxcurrent", "type": "float", "description": "the maximum current before any booster or converter"},
|
{"path": "set/maxcurrent", "type": "float", "readonly": false, "cmd": "run tt", "description": "the maximum current before any booster or converter"},
|
||||||
{"path": "set/manualpower", "type": "float", "readonly": false, "cmd": "tt set/manualpower"},
|
{"path": "set/manualpower", "type": "float", "readonly": false, "cmd": "tt set/manualpower"},
|
||||||
{"path": "set/power", "type": "float"},
|
{"path": "set/power", "type": "float", "readonly": false, "cmd": "run tt"},
|
||||||
{"path": "set/prop", "type": "float", "readonly": false, "cmd": "tt set/prop", "description": "bigger means more gain"},
|
{"path": "set/prop", "type": "float", "readonly": false, "cmd": "tt set/prop", "description": "bigger means more gain"},
|
||||||
{"path": "set/integ", "type": "float", "readonly": false, "cmd": "tt set/integ", "description": "bigger means faster"},
|
{"path": "set/integ", "type": "float", "readonly": false, "cmd": "tt set/integ", "description": "bigger means faster"},
|
||||||
{"path": "set/deriv", "type": "float", "readonly": false, "cmd": "tt set/deriv"},
|
{"path": "set/deriv", "type": "float", "readonly": false, "cmd": "tt set/deriv"},
|
||||||
{"path": "setsamp", "type": "float", "readonly": false, "cmd": "tt setsamp", "kids": 18},
|
{"path": "setsamp", "type": "float", "readonly": false, "cmd": "tt setsamp", "kids": 18},
|
||||||
{"path": "setsamp/mode", "type": "enum", "enum": {"disabled": -1, "off": 0, "controlling": 1, "manual": 2}, "readonly": false, "cmd": "tt setsamp/mode"},
|
{"path": "setsamp/mode", "type": "enum", "enum": {"disabled": -1, "off": 0, "controlling": 1, "manual": 2}, "readonly": false, "cmd": "tt setsamp/mode"},
|
||||||
{"path": "setsamp/reg", "type": "float"},
|
{"path": "setsamp/reg", "type": "float", "readonly": false, "cmd": "run tt"},
|
||||||
{"path": "setsamp/ramp", "type": "float", "readonly": false, "cmd": "tt setsamp/ramp", "description": "maximum ramp in K/min (0: ramp off)"},
|
{"path": "setsamp/ramp", "type": "float", "readonly": false, "cmd": "tt setsamp/ramp", "description": "maximum ramp in K/min (0: ramp off)"},
|
||||||
{"path": "setsamp/wramp", "type": "float", "readonly": false, "cmd": "tt setsamp/wramp"},
|
{"path": "setsamp/wramp", "type": "float", "readonly": false, "cmd": "tt setsamp/wramp"},
|
||||||
{"path": "setsamp/smooth", "type": "float", "readonly": false, "cmd": "tt setsamp/smooth", "description": "smooth time (minutes)"},
|
{"path": "setsamp/smooth", "type": "float", "readonly": false, "cmd": "tt setsamp/smooth", "description": "smooth time (minutes)"},
|
||||||
@ -72,16 +72,16 @@
|
|||||||
{"path": "setsamp/resist", "type": "float", "readonly": false, "cmd": "tt setsamp/resist"},
|
{"path": "setsamp/resist", "type": "float", "readonly": false, "cmd": "tt setsamp/resist"},
|
||||||
{"path": "setsamp/maxheater", "type": "text", "readonly": false, "cmd": "tt setsamp/maxheater", "description": "maximum heater limit, units should be given without space: W, mW, A, mA"},
|
{"path": "setsamp/maxheater", "type": "text", "readonly": false, "cmd": "tt setsamp/maxheater", "description": "maximum heater limit, units should be given without space: W, mW, A, mA"},
|
||||||
{"path": "setsamp/linearpower", "type": "float", "readonly": false, "cmd": "tt setsamp/linearpower", "description": "when not 0, it is the maximum effective power, and the power is linear to the heater output"},
|
{"path": "setsamp/linearpower", "type": "float", "readonly": false, "cmd": "tt setsamp/linearpower", "description": "when not 0, it is the maximum effective power, and the power is linear to the heater output"},
|
||||||
{"path": "setsamp/maxpowerlim", "type": "float", "description": "the maximum power limit (before any booster or converter)"},
|
{"path": "setsamp/maxpowerlim", "type": "float", "readonly": false, "cmd": "run tt", "description": "the maximum power limit (before any booster or converter)"},
|
||||||
{"path": "setsamp/maxpower", "type": "float", "readonly": false, "cmd": "tt setsamp/maxpower", "description": "maximum power [W]"},
|
{"path": "setsamp/maxpower", "type": "float", "readonly": false, "cmd": "tt setsamp/maxpower", "description": "maximum power [W]"},
|
||||||
{"path": "setsamp/maxcurrent", "type": "float", "description": "the maximum current before any booster or converter"},
|
{"path": "setsamp/maxcurrent", "type": "float", "readonly": false, "cmd": "run tt", "description": "the maximum current before any booster or converter"},
|
||||||
{"path": "setsamp/manualpower", "type": "float", "readonly": false, "cmd": "tt setsamp/manualpower"},
|
{"path": "setsamp/manualpower", "type": "float", "readonly": false, "cmd": "tt setsamp/manualpower"},
|
||||||
{"path": "setsamp/power", "type": "float"},
|
{"path": "setsamp/power", "type": "float", "readonly": false, "cmd": "run tt"},
|
||||||
{"path": "setsamp/prop", "type": "float", "readonly": false, "cmd": "tt setsamp/prop", "description": "bigger means more gain"},
|
{"path": "setsamp/prop", "type": "float", "readonly": false, "cmd": "tt setsamp/prop", "description": "bigger means more gain"},
|
||||||
{"path": "setsamp/integ", "type": "float", "readonly": false, "cmd": "tt setsamp/integ", "description": "bigger means faster"},
|
{"path": "setsamp/integ", "type": "float", "readonly": false, "cmd": "tt setsamp/integ", "description": "bigger means faster"},
|
||||||
{"path": "setsamp/deriv", "type": "float", "readonly": false, "cmd": "tt setsamp/deriv"},
|
{"path": "setsamp/deriv", "type": "float", "readonly": false, "cmd": "tt setsamp/deriv"},
|
||||||
{"path": "display", "type": "text", "readonly": false, "cmd": "tt display"},
|
{"path": "display", "type": "text", "readonly": false, "cmd": "tt display"},
|
||||||
{"path": "remote", "type": "bool"}]},
|
{"path": "remote", "type": "bool", "readonly": false, "cmd": "run tt"}]},
|
||||||
|
|
||||||
"cc": {"base": "/cc", "params": [
|
"cc": {"base": "/cc", "params": [
|
||||||
{"path": "", "type": "bool", "kids": 96},
|
{"path": "", "type": "bool", "kids": 96},
|
||||||
@ -239,16 +239,16 @@
|
|||||||
{"path": "", "type": "enum", "enum": {"xds35_auto": 0, "xds35_manual": 1, "sv65": 2, "other": 3, "no": -1}, "readonly": false, "cmd": "hepump", "description": "xds35: scroll pump, sv65: leybold", "kids": 10},
|
{"path": "", "type": "enum", "enum": {"xds35_auto": 0, "xds35_manual": 1, "sv65": 2, "other": 3, "no": -1}, "readonly": false, "cmd": "hepump", "description": "xds35: scroll pump, sv65: leybold", "kids": 10},
|
||||||
{"path": "send", "type": "text", "readonly": false, "cmd": "hepump send", "visibility": 3},
|
{"path": "send", "type": "text", "readonly": false, "cmd": "hepump send", "visibility": 3},
|
||||||
{"path": "status", "type": "text", "visibility": 3},
|
{"path": "status", "type": "text", "visibility": 3},
|
||||||
{"path": "running", "type": "bool", "readonly": false, "cmd": "hepump running", "visibility": 3},
|
{"path": "running", "type": "bool", "readonly": false, "cmd": "hepump running"},
|
||||||
{"path": "eco", "type": "bool", "readonly": false, "cmd": "hepump eco", "visibility": 3},
|
{"path": "eco", "type": "bool", "readonly": false, "cmd": "hepump eco"},
|
||||||
{"path": "auto", "type": "bool", "readonly": false, "cmd": "hepump auto", "visibility": 3},
|
{"path": "auto", "type": "bool", "readonly": false, "cmd": "hepump auto"},
|
||||||
{"path": "valve", "type": "enum", "enum": {"closed": 0, "closing": 1, "opening": 2, "opened": 3, "undefined": 4}, "readonly": false, "cmd": "hepump valve", "visibility": 3},
|
{"path": "valve", "type": "enum", "enum": {"closed": 0, "closing": 1, "opening": 2, "opened": 3, "undefined": 4}, "readonly": false, "cmd": "hepump valve"},
|
||||||
{"path": "eco_t_lim", "type": "float", "readonly": false, "cmd": "hepump eco_t_lim", "description": "switch off eco mode when T_set < eco_t_lim and T < eco_t_lim * 2", "visibility": 3},
|
{"path": "eco_t_lim", "type": "float", "readonly": false, "cmd": "hepump eco_t_lim", "description": "switch off eco mode when T_set < eco_t_lim and T < eco_t_lim * 2"},
|
||||||
{"path": "calib", "type": "float", "readonly": false, "cmd": "hepump calib", "visibility": 3},
|
{"path": "calib", "type": "float", "readonly": false, "cmd": "hepump calib", "visibility": 3},
|
||||||
{"path": "health", "type": "float"}]},
|
{"path": "health", "type": "float"}]},
|
||||||
|
|
||||||
"hemot": {"base": "/hepump/hemot", "params": [
|
"hemot": {"base": "/hepump/hemot", "params": [
|
||||||
{"path": "", "type": "float", "readonly": false, "cmd": "run hemot", "visibility": 3, "kids": 30},
|
{"path": "", "type": "float", "readonly": false, "cmd": "run hemot", "kids": 30},
|
||||||
{"path": "send", "type": "text", "readonly": false, "cmd": "hemot send", "visibility": 3},
|
{"path": "send", "type": "text", "readonly": false, "cmd": "hemot send", "visibility": 3},
|
||||||
{"path": "status", "type": "text", "visibility": 3},
|
{"path": "status", "type": "text", "visibility": 3},
|
||||||
{"path": "is_running", "type": "int", "readonly": false, "cmd": "hemot is_running", "visibility": 3},
|
{"path": "is_running", "type": "int", "readonly": false, "cmd": "hemot is_running", "visibility": 3},
|
||||||
@ -280,6 +280,16 @@
|
|||||||
{"path": "customadr", "type": "text", "readonly": false, "cmd": "hemot customadr"},
|
{"path": "customadr", "type": "text", "readonly": false, "cmd": "hemot customadr"},
|
||||||
{"path": "custompar", "type": "float", "readonly": false, "cmd": "hemot custompar"}]},
|
{"path": "custompar", "type": "float", "readonly": false, "cmd": "hemot custompar"}]},
|
||||||
|
|
||||||
|
"nvflow": {"base": "/nvflow", "params": [
|
||||||
|
{"path": "", "type": "float", "kids": 7},
|
||||||
|
{"path": "send", "type": "text", "readonly": false, "cmd": "nvflow send", "visibility": 3},
|
||||||
|
{"path": "status", "type": "text", "visibility": 3},
|
||||||
|
{"path": "stddev", "type": "float"},
|
||||||
|
{"path": "nsamples", "type": "int", "readonly": false, "cmd": "nvflow nsamples"},
|
||||||
|
{"path": "offset", "type": "float", "readonly": false, "cmd": "nvflow offset"},
|
||||||
|
{"path": "scale", "type": "float", "readonly": false, "cmd": "nvflow scale"},
|
||||||
|
{"path": "save", "type": "bool", "readonly": false, "cmd": "nvflow save", "description": "unchecked: current calib is not saved. set checked: save calib"}]},
|
||||||
|
|
||||||
"ln2fill": {"base": "/ln2fill", "params": [
|
"ln2fill": {"base": "/ln2fill", "params": [
|
||||||
{"path": "", "type": "enum", "enum": {"watching": 0, "fill": 1, "inactive": 2}, "readonly": false, "cmd": "ln2fill", "kids": 14},
|
{"path": "", "type": "enum", "enum": {"watching": 0, "fill": 1, "inactive": 2}, "readonly": false, "cmd": "ln2fill", "kids": 14},
|
||||||
{"path": "send", "type": "text", "readonly": false, "cmd": "ln2fill send", "visibility": 3},
|
{"path": "send", "type": "text", "readonly": false, "cmd": "ln2fill send", "visibility": 3},
|
||||||
@ -317,32 +327,32 @@
|
|||||||
{"path": "vext", "type": "float"}]},
|
{"path": "vext", "type": "float"}]},
|
||||||
|
|
||||||
"mf": {"base": "/mf", "params": [
|
"mf": {"base": "/mf", "params": [
|
||||||
{"path": "", "type": "float", "kids": 26},
|
{"path": "", "type": "float", "readonly": false, "cmd": "run mf", "kids": 26},
|
||||||
{"path": "persmode", "type": "int", "readonly": false, "cmd": "mf persmode"},
|
{"path": "persmode", "type": "int", "readonly": false, "cmd": "mf persmode"},
|
||||||
{"path": "perswitch", "type": "int"},
|
{"path": "perswitch", "type": "int", "readonly": false, "cmd": "run mf"},
|
||||||
{"path": "nowait", "type": "int", "readonly": false, "cmd": "mf nowait"},
|
{"path": "nowait", "type": "int", "readonly": false, "cmd": "mf nowait"},
|
||||||
{"path": "maxlimit", "type": "float", "visibility": 3},
|
{"path": "maxlimit", "type": "float", "readonly": false, "cmd": "run mf", "visibility": 3},
|
||||||
{"path": "limit", "type": "float", "readonly": false, "cmd": "mf limit"},
|
{"path": "limit", "type": "float", "readonly": false, "cmd": "mf limit"},
|
||||||
{"path": "ramp", "type": "float", "readonly": false, "cmd": "mf ramp"},
|
{"path": "ramp", "type": "float", "readonly": false, "cmd": "mf ramp"},
|
||||||
{"path": "perscurrent", "type": "float", "readonly": false, "cmd": "mf perscurrent"},
|
{"path": "perscurrent", "type": "float", "readonly": false, "cmd": "mf perscurrent"},
|
||||||
{"path": "perslimit", "type": "float", "readonly": false, "cmd": "mf perslimit"},
|
{"path": "perslimit", "type": "float", "readonly": false, "cmd": "mf perslimit"},
|
||||||
{"path": "perswait", "type": "int", "readonly": false, "cmd": "mf perswait"},
|
{"path": "perswait", "type": "int", "readonly": false, "cmd": "mf perswait"},
|
||||||
{"path": "persdelay", "type": "int", "readonly": false, "cmd": "mf persdelay"},
|
{"path": "persdelay", "type": "int", "readonly": false, "cmd": "mf persdelay"},
|
||||||
{"path": "current", "type": "float"},
|
{"path": "current", "type": "float", "readonly": false, "cmd": "run mf"},
|
||||||
{"path": "measured", "type": "float"},
|
{"path": "measured", "type": "float", "readonly": false, "cmd": "run mf"},
|
||||||
{"path": "voltage", "type": "float"},
|
{"path": "voltage", "type": "float", "readonly": false, "cmd": "run mf"},
|
||||||
{"path": "lastfield", "type": "float", "visibility": 3},
|
{"path": "lastfield", "type": "float", "readonly": false, "cmd": "run mf", "visibility": 3},
|
||||||
{"path": "ampRamp", "type": "float", "visibility": 3},
|
{"path": "ampRamp", "type": "float", "readonly": false, "cmd": "run mf", "visibility": 3},
|
||||||
{"path": "inductance", "type": "float", "visibility": 3},
|
{"path": "inductance", "type": "float", "readonly": false, "cmd": "run mf", "visibility": 3},
|
||||||
{"path": "trainedTo", "type": "float", "readonly": false, "cmd": "mf trainedTo"},
|
{"path": "trainedTo", "type": "float", "readonly": false, "cmd": "mf trainedTo"},
|
||||||
{"path": "trainMode", "type": "int"},
|
{"path": "trainMode", "type": "int", "readonly": false, "cmd": "run mf"},
|
||||||
{"path": "external", "type": "int", "readonly": false, "cmd": "mf external"},
|
{"path": "external", "type": "int", "readonly": false, "cmd": "mf external"},
|
||||||
{"path": "startScript", "type": "text", "readonly": false, "cmd": "mf startScript", "visibility": 3},
|
{"path": "startScript", "type": "text", "readonly": false, "cmd": "mf startScript", "visibility": 3},
|
||||||
{"path": "is_running", "type": "int", "visibility": 3},
|
{"path": "is_running", "type": "int", "readonly": false, "cmd": "run mf", "visibility": 3},
|
||||||
{"path": "verbose", "type": "int", "readonly": false, "cmd": "mf verbose", "visibility": 3},
|
{"path": "verbose", "type": "int", "readonly": false, "cmd": "mf verbose", "visibility": 3},
|
||||||
{"path": "driver", "type": "text", "visibility": 3},
|
{"path": "driver", "type": "text", "readonly": false, "cmd": "run mf", "visibility": 3},
|
||||||
{"path": "creationCmd", "type": "text", "visibility": 3},
|
{"path": "creationCmd", "type": "text", "readonly": false, "cmd": "run mf", "visibility": 3},
|
||||||
{"path": "targetValue", "type": "float"},
|
{"path": "targetValue", "type": "float", "readonly": false, "cmd": "run mf"},
|
||||||
{"path": "status", "type": "text", "readonly": false, "cmd": "mf status", "visibility": 3}]},
|
{"path": "status", "type": "text", "readonly": false, "cmd": "mf status", "visibility": 3}]},
|
||||||
|
|
||||||
"lev": {"base": "/lev", "params": [
|
"lev": {"base": "/lev", "params": [
|
||||||
@ -350,4 +360,9 @@
|
|||||||
{"path": "send", "type": "text", "readonly": false, "cmd": "lev send", "visibility": 3},
|
{"path": "send", "type": "text", "readonly": false, "cmd": "lev send", "visibility": 3},
|
||||||
{"path": "status", "type": "text", "visibility": 3},
|
{"path": "status", "type": "text", "visibility": 3},
|
||||||
{"path": "mode", "type": "enum", "enum": {"slow": 0, "fast (switches to slow automatically after filling)": 1}, "readonly": false, "cmd": "lev mode"},
|
{"path": "mode", "type": "enum", "enum": {"slow": 0, "fast (switches to slow automatically after filling)": 1}, "readonly": false, "cmd": "lev mode"},
|
||||||
{"path": "n2", "type": "float"}]}}
|
{"path": "n2", "type": "float"}]},
|
||||||
|
|
||||||
|
"prep0": {"base": "/prep0", "params": [
|
||||||
|
{"path": "", "type": "text", "readonly": false, "cmd": "prep0", "kids": 2},
|
||||||
|
{"path": "send", "type": "text", "readonly": false, "cmd": "prep0 send", "visibility": 3},
|
||||||
|
{"path": "status", "type": "text", "visibility": 3}]}}
|
||||||
|
@ -42,10 +42,15 @@ digits = 2
|
|||||||
scale_factor = 0.0156
|
scale_factor = 0.0156
|
||||||
offset = 15
|
offset = 15
|
||||||
|
|
||||||
|
[res_io]
|
||||||
|
description = io to lakeshore
|
||||||
|
class = secop_psi.ls340res.LscIO
|
||||||
|
uri = tcp://192.168.127.254:3003
|
||||||
|
|
||||||
[res]
|
[res]
|
||||||
description = temperature on uniax stick
|
description = temperature on uniax stick
|
||||||
class = secop_psi.ls340res.ResChannel
|
class = secop_psi.ls340res.ResChannel
|
||||||
uri = tcp://192.168.127.254:3003
|
io = res_io
|
||||||
channel = A
|
channel = A
|
||||||
|
|
||||||
[T]
|
[T]
|
||||||
|
74
debian/changelog
vendored
74
debian/changelog
vendored
@ -1,3 +1,77 @@
|
|||||||
|
secop-core (0.13.1) focal; urgency=medium
|
||||||
|
|
||||||
|
[ Markus Zolliker ]
|
||||||
|
* an enum with value 0 should be interpreted as False
|
||||||
|
* make startup faster in case of errors
|
||||||
|
|
||||||
|
[ Enrico Faulhaber ]
|
||||||
|
* secop_mlz: minor rework entangle client
|
||||||
|
|
||||||
|
-- Markus Zolliker <jenkins@jenkins02.admin.frm2.tum.de> Tue, 02 Aug 2022 15:31:52 +0200
|
||||||
|
|
||||||
|
secop-core (0.13.0) focal; urgency=medium
|
||||||
|
|
||||||
|
[ Georg Brandl ]
|
||||||
|
* debian: fix email addresses in changelog
|
||||||
|
|
||||||
|
[ Markus Zolliker ]
|
||||||
|
* various small changes
|
||||||
|
* automatic saving of persistent parameters
|
||||||
|
* add more tests and fixes for command inheritance
|
||||||
|
* entangle.AnalogOutput: fix window mechanism
|
||||||
|
* remote logging (issue 46)
|
||||||
|
* add timeouts to MultiEvents
|
||||||
|
* introduce general config file
|
||||||
|
* improve handling of module init methods
|
||||||
|
* check for bad read_* and write_* methods
|
||||||
|
* change name of read_hw_status method in sequencer mixin
|
||||||
|
* fix doc (stringio - > io)
|
||||||
|
* enhance logging
|
||||||
|
* UniqueObject
|
||||||
|
* ReadHandler and WriteHandler decorators
|
||||||
|
* do not convert string to float
|
||||||
|
* check for problematic value range
|
||||||
|
* unify name and module on Attached property
|
||||||
|
* ppms: replace IOHandler by Read/WriteHandler
|
||||||
|
* fix handling commands
|
||||||
|
* common read/write handlers
|
||||||
|
* implement a state machine
|
||||||
|
* proper return value in handler read_* methods
|
||||||
|
* new poll mechanism
|
||||||
|
* support for fast poll when busy
|
||||||
|
* various small fixes
|
||||||
|
* reset connection on identification
|
||||||
|
* improve softcal
|
||||||
|
* move markdown to requirements-dev.txt
|
||||||
|
* improve k2601b driver
|
||||||
|
* fix and improved Attached
|
||||||
|
* fix error in write wrapper and more
|
||||||
|
* support write_ method on readonly param and more
|
||||||
|
* init generalConfig.defaults only in secop-server
|
||||||
|
* HasConvergence mixin
|
||||||
|
* avoid race conditions in read_*/write_* methods
|
||||||
|
* reintroduced individual init of generalConfig.defaults
|
||||||
|
* fix statemachine
|
||||||
|
* use a common poller thread for modules sharing io
|
||||||
|
* motor valve using trinamic motor
|
||||||
|
* improved trinamic driver
|
||||||
|
* fix error in secop.logging
|
||||||
|
* avoid deadlock in proxy
|
||||||
|
* improve poller error handling
|
||||||
|
* support for OI mercury series
|
||||||
|
* add 'ts' to the ppms simulation
|
||||||
|
* allow a configfile path as single argument to secop-server
|
||||||
|
* fix keithley 2601b after tests
|
||||||
|
* channel switcher for Lakeshore 370 with scanner
|
||||||
|
* feature implementation
|
||||||
|
* allow to convert numpy arrays to ArrayOf
|
||||||
|
* remove IOHandler stuff
|
||||||
|
|
||||||
|
[ Enrico Faulhaber ]
|
||||||
|
* default unit to UTF8
|
||||||
|
|
||||||
|
-- Georg Brandl <jenkins@frm2.tum.de> Tue, 02 Aug 2022 09:47:06 +0200
|
||||||
|
|
||||||
secop-core (0.12.4) focal; urgency=medium
|
secop-core (0.12.4) focal; urgency=medium
|
||||||
|
|
||||||
* fix command inheritance
|
* fix command inheritance
|
||||||
|
@ -356,7 +356,7 @@ class SecopClient(ProxyClient):
|
|||||||
except ConnectionClosed:
|
except ConnectionClosed:
|
||||||
pass
|
pass
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.log.error('rxthread ended with %s' % e)
|
self.log.error('rxthread ended with %r', e)
|
||||||
self._rxthread = None
|
self._rxthread = None
|
||||||
self.disconnect(False)
|
self.disconnect(False)
|
||||||
if self._shutdown:
|
if self._shutdown:
|
||||||
@ -490,7 +490,7 @@ class SecopClient(ProxyClient):
|
|||||||
|
|
||||||
def _unhandled_message(self, action, ident, data):
|
def _unhandled_message(self, action, ident, data):
|
||||||
if not self.callback(None, 'unhandledMessage', action, ident, data):
|
if not self.callback(None, 'unhandledMessage', action, ident, data):
|
||||||
self.log.warning('unhandled message: %s %s %r' % (action, ident, data))
|
self.log.warning('unhandled message: %s %s %r', action, ident, data)
|
||||||
|
|
||||||
def _set_state(self, online, state=None):
|
def _set_state(self, online, state=None):
|
||||||
# remark: reconnecting is treated as online
|
# remark: reconnecting is treated as online
|
||||||
|
@ -23,10 +23,11 @@
|
|||||||
|
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
import json
|
import re
|
||||||
from queue import Queue
|
from queue import Queue
|
||||||
from secop.client import SecopClient
|
from secop.client import SecopClient
|
||||||
from secop.errors import SECoPError
|
from secop.errors import SECoPError
|
||||||
|
from secop.datatypes import get_datatype
|
||||||
|
|
||||||
USAGE = """
|
USAGE = """
|
||||||
Usage:
|
Usage:
|
||||||
@ -58,10 +59,15 @@ class Logger:
|
|||||||
if lev == loglevel:
|
if lev == loglevel:
|
||||||
func = self.emit
|
func = self.emit
|
||||||
setattr(self, lev, func)
|
setattr(self, lev, func)
|
||||||
|
self._minute = 0
|
||||||
|
|
||||||
@staticmethod
|
def emit(self, fmt, *args, **kwds):
|
||||||
def emit(fmt, *args, **kwds):
|
now = time.time()
|
||||||
print(str(fmt) % args)
|
minute = now // 60
|
||||||
|
if minute != self._minute:
|
||||||
|
self._minute = minute
|
||||||
|
print(time.strftime('--- %H:%M:%S ---', time.localtime(now)))
|
||||||
|
print('%6.3f' % (now % 60.0), str(fmt) % args)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def noop(fmt, *args, **kwds):
|
def noop(fmt, *args, **kwds):
|
||||||
@ -77,6 +83,8 @@ class PrettyFloat(float):
|
|||||||
|
|
||||||
|
|
||||||
class Module:
|
class Module:
|
||||||
|
_log_pattern = re.compile('.*')
|
||||||
|
|
||||||
def __init__(self, name, secnode):
|
def __init__(self, name, secnode):
|
||||||
self._name = name
|
self._name = name
|
||||||
self._secnode = secnode
|
self._secnode = secnode
|
||||||
@ -89,15 +97,12 @@ class Module:
|
|||||||
|
|
||||||
def _one_line(self, pname, minwid=0):
|
def _one_line(self, pname, minwid=0):
|
||||||
"""return <module>.<param> = <value> truncated to one line"""
|
"""return <module>.<param> = <value> truncated to one line"""
|
||||||
|
param = getattr(type(self), pname)
|
||||||
try:
|
try:
|
||||||
value = getattr(self, pname)
|
value = getattr(self, pname)
|
||||||
# make floats appear with 7 digits only
|
r = param.format(value)
|
||||||
r = repr(json.loads(json.dumps(value), parse_float=PrettyFloat))
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
r = repr(e)
|
r = repr(e)
|
||||||
unit = getattr(type(self), pname).unit
|
|
||||||
if unit:
|
|
||||||
r += ' %s' % unit
|
|
||||||
pname = pname.ljust(minwid)
|
pname = pname.ljust(minwid)
|
||||||
vallen = 113 - len(self._name) - len(pname)
|
vallen = 113 - len(self._name) - len(pname)
|
||||||
if len(r) > vallen:
|
if len(r) > vallen:
|
||||||
@ -174,13 +179,21 @@ class Module:
|
|||||||
'\n'.join(self._one_line(k, wid) for k in self._parameters),
|
'\n'.join(self._one_line(k, wid) for k in self._parameters),
|
||||||
', '.join(k + '()' for k in self._commands))
|
', '.join(k + '()' for k in self._commands))
|
||||||
|
|
||||||
|
def logging(self, level='comlog', pattern='.*'):
|
||||||
|
self._log_pattern = re.compile(pattern)
|
||||||
|
self._secnode.request('logging', self._name, level)
|
||||||
|
|
||||||
|
def handle_log_message_(self, data):
|
||||||
|
if self._log_pattern.match(data):
|
||||||
|
self._secnode.log.info('%s: %r', self._name, data)
|
||||||
|
|
||||||
|
|
||||||
class Param:
|
class Param:
|
||||||
def __init__(self, name, unit=None):
|
def __init__(self, name, datainfo):
|
||||||
self.name = name
|
self.name = name
|
||||||
self.prev = None
|
self.prev = None
|
||||||
self.prev_time = 0
|
self.prev_time = 0
|
||||||
self.unit = unit
|
self.datatype = get_datatype(datainfo)
|
||||||
|
|
||||||
def __get__(self, obj, owner):
|
def __get__(self, obj, owner):
|
||||||
if obj is None:
|
if obj is None:
|
||||||
@ -198,6 +211,9 @@ class Param:
|
|||||||
except SECoPError as e:
|
except SECoPError as e:
|
||||||
obj._secnode.log.error(repr(e))
|
obj._secnode.log.error(repr(e))
|
||||||
|
|
||||||
|
def format(self, value):
|
||||||
|
return self.datatype.format_value(value)
|
||||||
|
|
||||||
|
|
||||||
class Command:
|
class Command:
|
||||||
def __init__(self, name, modname, secnode):
|
def __init__(self, name, modname, secnode):
|
||||||
@ -250,14 +266,24 @@ class Client(SecopClient):
|
|||||||
self.log.info('overwrite module %s', modname)
|
self.log.info('overwrite module %s', modname)
|
||||||
attrs = {}
|
attrs = {}
|
||||||
for pname, pinfo in moddesc['parameters'].items():
|
for pname, pinfo in moddesc['parameters'].items():
|
||||||
unit = pinfo['datainfo'].get('unit')
|
attrs[pname] = Param(pname, pinfo['datainfo'])
|
||||||
attrs[pname] = Param(pname, unit)
|
|
||||||
for cname in moddesc['commands']:
|
for cname in moddesc['commands']:
|
||||||
attrs[cname] = Command(cname, modname, self)
|
attrs[cname] = Command(cname, modname, self)
|
||||||
mobj = type('M_%s' % modname, (Module,), attrs)(modname, self)
|
mobj = type('M_%s' % modname, (Module,), attrs)(modname, self)
|
||||||
if 'status' in mobj._parameters:
|
if 'status' in mobj._parameters:
|
||||||
self.register_callback((modname, 'status'), updateEvent=mobj._status_value_update)
|
self.register_callback((modname, 'status'), updateEvent=mobj._status_value_update)
|
||||||
self.register_callback((modname, 'value'), updateEvent=mobj._status_value_update)
|
self.register_callback((modname, 'value'), updateEvent=mobj._status_value_update)
|
||||||
|
|
||||||
setattr(main, modname, mobj)
|
setattr(main, modname, mobj)
|
||||||
|
self.register_callback(None, self.unhandledMessage)
|
||||||
self.log.info('%s', USAGE)
|
self.log.info('%s', USAGE)
|
||||||
|
|
||||||
|
def unhandledMessage(self, action, ident, data):
|
||||||
|
"""handle logging messages"""
|
||||||
|
if action == 'log':
|
||||||
|
modname = ident.split(':')[0]
|
||||||
|
modobj = getattr(main, modname, None)
|
||||||
|
if modobj:
|
||||||
|
modobj.handle_log_message_(data)
|
||||||
|
return
|
||||||
|
self.log.info('module %s not found', modname)
|
||||||
|
self.log.info('unhandled: %s %s %r', action, ident, data)
|
||||||
|
@ -124,6 +124,9 @@ class DataType(HasProperties):
|
|||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def set_main_unit(self, unit):
|
||||||
|
"""replace $ in unit by argument"""
|
||||||
|
|
||||||
|
|
||||||
class Stub(DataType):
|
class Stub(DataType):
|
||||||
"""incomplete datatype, to be replaced with a proper one later during module load
|
"""incomplete datatype, to be replaced with a proper one later during module load
|
||||||
@ -155,9 +158,17 @@ class Stub(DataType):
|
|||||||
prop.datatype = globals()[stub.name](*stub.args, **stub.kwds)
|
prop.datatype = globals()[stub.name](*stub.args, **stub.kwds)
|
||||||
|
|
||||||
|
|
||||||
|
class HasUnit:
|
||||||
|
unit = Property('physical unit', Stub('StringType', isUTF8=True), extname='unit', default='')
|
||||||
|
|
||||||
|
def set_main_unit(self, unit):
|
||||||
|
if '$' in self.unit:
|
||||||
|
self.setProperty('unit', self.unit.replace('$', unit))
|
||||||
|
|
||||||
|
|
||||||
# SECoP types:
|
# SECoP types:
|
||||||
|
|
||||||
class FloatRange(DataType):
|
class FloatRange(HasUnit, DataType):
|
||||||
"""(restricted) float type
|
"""(restricted) float type
|
||||||
|
|
||||||
:param minval: (property **min**)
|
:param minval: (property **min**)
|
||||||
@ -166,7 +177,6 @@ class FloatRange(DataType):
|
|||||||
"""
|
"""
|
||||||
min = Property('low limit', Stub('FloatRange'), extname='min', default=-sys.float_info.max)
|
min = Property('low limit', Stub('FloatRange'), extname='min', default=-sys.float_info.max)
|
||||||
max = Property('high limit', Stub('FloatRange'), extname='max', default=sys.float_info.max)
|
max = Property('high limit', Stub('FloatRange'), extname='max', default=sys.float_info.max)
|
||||||
unit = Property('physical unit', Stub('StringType', isUTF8=True), extname='unit', default='')
|
|
||||||
fmtstr = Property('format string', Stub('StringType'), extname='fmtstr', default='%g')
|
fmtstr = Property('format string', Stub('StringType'), extname='fmtstr', default='%g')
|
||||||
absolute_resolution = Property('absolute resolution', Stub('FloatRange', 0),
|
absolute_resolution = Property('absolute resolution', Stub('FloatRange', 0),
|
||||||
extname='absolute_resolution', default=0.0)
|
extname='absolute_resolution', default=0.0)
|
||||||
@ -331,7 +341,7 @@ class IntRange(DataType):
|
|||||||
raise BadValueError('incompatible datatypes')
|
raise BadValueError('incompatible datatypes')
|
||||||
|
|
||||||
|
|
||||||
class ScaledInteger(DataType):
|
class ScaledInteger(HasUnit, DataType):
|
||||||
"""scaled integer (= fixed resolution float) type
|
"""scaled integer (= fixed resolution float) type
|
||||||
|
|
||||||
:param minval: (property **min**)
|
:param minval: (property **min**)
|
||||||
@ -344,7 +354,6 @@ class ScaledInteger(DataType):
|
|||||||
scale = Property('scale factor', FloatRange(sys.float_info.min), extname='scale', mandatory=True)
|
scale = Property('scale factor', FloatRange(sys.float_info.min), extname='scale', mandatory=True)
|
||||||
min = Property('low limit', FloatRange(), extname='min', mandatory=True)
|
min = Property('low limit', FloatRange(), extname='min', mandatory=True)
|
||||||
max = Property('high limit', FloatRange(), extname='max', mandatory=True)
|
max = Property('high limit', FloatRange(), extname='max', mandatory=True)
|
||||||
unit = Property('physical unit', Stub('StringType', isUTF8=True), extname='unit', default='')
|
|
||||||
fmtstr = Property('format string', Stub('StringType'), extname='fmtstr', default='%g')
|
fmtstr = Property('format string', Stub('StringType'), extname='fmtstr', default='%g')
|
||||||
absolute_resolution = Property('absolute resolution', FloatRange(0),
|
absolute_resolution = Property('absolute resolution', FloatRange(0),
|
||||||
extname='absolute_resolution', default=0.0)
|
extname='absolute_resolution', default=0.0)
|
||||||
@ -806,6 +815,9 @@ class ArrayOf(DataType):
|
|||||||
except AttributeError:
|
except AttributeError:
|
||||||
raise BadValueError('incompatible datatypes') from None
|
raise BadValueError('incompatible datatypes') from None
|
||||||
|
|
||||||
|
def set_main_unit(self, unit):
|
||||||
|
self.members.set_main_unit(unit)
|
||||||
|
|
||||||
|
|
||||||
class TupleOf(DataType):
|
class TupleOf(DataType):
|
||||||
"""data structure with fields of inhomogeneous type
|
"""data structure with fields of inhomogeneous type
|
||||||
@ -872,6 +884,10 @@ class TupleOf(DataType):
|
|||||||
for a, b in zip(self.members, other.members):
|
for a, b in zip(self.members, other.members):
|
||||||
a.compatible(b)
|
a.compatible(b)
|
||||||
|
|
||||||
|
def set_main_unit(self, unit):
|
||||||
|
for member in self.members:
|
||||||
|
member.set_main_unit(unit)
|
||||||
|
|
||||||
|
|
||||||
class ImmutableDict(dict):
|
class ImmutableDict(dict):
|
||||||
def _no(self, *args, **kwds):
|
def _no(self, *args, **kwds):
|
||||||
@ -961,6 +977,10 @@ class StructOf(DataType):
|
|||||||
except (AttributeError, TypeError, KeyError):
|
except (AttributeError, TypeError, KeyError):
|
||||||
raise BadValueError('incompatible datatypes') from None
|
raise BadValueError('incompatible datatypes') from None
|
||||||
|
|
||||||
|
def set_main_unit(self, unit):
|
||||||
|
for member in self.members.values():
|
||||||
|
member.set_main_unit(unit)
|
||||||
|
|
||||||
|
|
||||||
class CommandType(DataType):
|
class CommandType(DataType):
|
||||||
"""command
|
"""command
|
||||||
|
32
secop/io.py
32
secop/io.py
@ -71,14 +71,6 @@ class HasIO(Module):
|
|||||||
elif not io:
|
elif not io:
|
||||||
raise ConfigError("Module %s needs a value for either 'uri' or 'io'" % name)
|
raise ConfigError("Module %s needs a value for either 'uri' or 'io'" % name)
|
||||||
|
|
||||||
def initModule(self):
|
|
||||||
try:
|
|
||||||
self.io.read_is_connected()
|
|
||||||
except (CommunicationFailedError, AttributeError):
|
|
||||||
# AttributeError: read_is_connected is not required for an io object
|
|
||||||
pass
|
|
||||||
super().initModule()
|
|
||||||
|
|
||||||
def communicate(self, *args):
|
def communicate(self, *args):
|
||||||
return self.io.communicate(*args)
|
return self.io.communicate(*args)
|
||||||
|
|
||||||
@ -118,6 +110,7 @@ class IOBase(Communicator):
|
|||||||
_conn = None
|
_conn = None
|
||||||
_last_error = None
|
_last_error = None
|
||||||
_lock = None
|
_lock = None
|
||||||
|
_last_connect_attempt = 0
|
||||||
|
|
||||||
def earlyInit(self):
|
def earlyInit(self):
|
||||||
super().earlyInit()
|
super().earlyInit()
|
||||||
@ -169,6 +162,17 @@ class IOBase(Communicator):
|
|||||||
return False
|
return False
|
||||||
return self.read_is_connected()
|
return self.read_is_connected()
|
||||||
|
|
||||||
|
def check_connection(self):
|
||||||
|
"""called before communicate"""
|
||||||
|
if not self.is_connected:
|
||||||
|
now = time.time()
|
||||||
|
if now >= self._last_connect_attempt + self.pollinterval:
|
||||||
|
# we do not try to reconnect more often than pollinterval
|
||||||
|
_last_connect_attempt = now
|
||||||
|
if self.read_is_connected():
|
||||||
|
return
|
||||||
|
raise SilentError('disconnected') from None
|
||||||
|
|
||||||
def registerReconnectCallback(self, name, func):
|
def registerReconnectCallback(self, name, func):
|
||||||
"""register reconnect callback
|
"""register reconnect callback
|
||||||
|
|
||||||
@ -250,11 +254,7 @@ class StringIO(IOBase):
|
|||||||
wait_before is respected for end_of_lines within a command.
|
wait_before is respected for end_of_lines within a command.
|
||||||
"""
|
"""
|
||||||
command = command.encode(self.encoding)
|
command = command.encode(self.encoding)
|
||||||
if not self.is_connected:
|
self.check_connection()
|
||||||
# do not try to reconnect here
|
|
||||||
# read_is_connected is doing this when called by its poller
|
|
||||||
self.read_is_connected() # try to reconnect
|
|
||||||
raise SilentError('disconnected') from None
|
|
||||||
try:
|
try:
|
||||||
with self._lock:
|
with self._lock:
|
||||||
# read garbage and wait before send
|
# read garbage and wait before send
|
||||||
@ -359,11 +359,7 @@ class BytesIO(IOBase):
|
|||||||
@Command((BLOBType(), IntRange(0)), result=BLOBType())
|
@Command((BLOBType(), IntRange(0)), result=BLOBType())
|
||||||
def communicate(self, request, replylen): # pylint: disable=arguments-differ
|
def communicate(self, request, replylen): # pylint: disable=arguments-differ
|
||||||
"""send a request and receive (at least) <replylen> bytes as reply"""
|
"""send a request and receive (at least) <replylen> bytes as reply"""
|
||||||
if not self.is_connected:
|
self.check_connection()
|
||||||
# do not try to reconnect here
|
|
||||||
# read_is_connected is doing this when called by its poller
|
|
||||||
self.read_is_connected() # try to reconnect
|
|
||||||
raise SilentError('disconnected') from None
|
|
||||||
try:
|
try:
|
||||||
with self._lock:
|
with self._lock:
|
||||||
# read garbage and wait before send
|
# read garbage and wait before send
|
||||||
|
@ -132,7 +132,8 @@ class StateMachine:
|
|||||||
:return: None (for custom cleanup functions this might be a new state)
|
:return: None (for custom cleanup functions this might be a new state)
|
||||||
"""
|
"""
|
||||||
if state.stopped: # stop or restart
|
if state.stopped: # stop or restart
|
||||||
state.log.debug('%sed in state %r', repr(state.stopped).lower(), state.status_string)
|
verb = 'stopped' if state.stopped is Stop else 'restarted'
|
||||||
|
state.log.debug('%s in state %r', verb, state.status_string)
|
||||||
else:
|
else:
|
||||||
state.log.warning('%r raised in state %r', state.last_error, state.status_string)
|
state.log.warning('%r raised in state %r', state.last_error, state.status_string)
|
||||||
|
|
||||||
@ -196,7 +197,7 @@ class StateMachine:
|
|||||||
self.log.debug('called %r %sexc=%r', self.cleanup,
|
self.log.debug('called %r %sexc=%r', self.cleanup,
|
||||||
'ret=%r ' % ret if ret else '', e)
|
'ret=%r ' % ret if ret else '', e)
|
||||||
if ret is None:
|
if ret is None:
|
||||||
self.log.debug('state: None')
|
self.log.debug('state: None after cleanup')
|
||||||
self.state = None
|
self.state = None
|
||||||
self._idle_event.set()
|
self._idle_event.set()
|
||||||
return None
|
return None
|
||||||
|
@ -30,7 +30,7 @@ from functools import wraps
|
|||||||
|
|
||||||
from secop.datatypes import ArrayOf, BoolType, EnumType, FloatRange, \
|
from secop.datatypes import ArrayOf, BoolType, EnumType, FloatRange, \
|
||||||
IntRange, StatusType, StringType, TextType, TupleOf, DiscouragedConversion
|
IntRange, StatusType, StringType, TextType, TupleOf, DiscouragedConversion
|
||||||
from secop.errors import BadValueError, ConfigError, \
|
from secop.errors import BadValueError, CommunicationFailedError, ConfigError, \
|
||||||
ProgrammingError, SECoPError, secop_error
|
ProgrammingError, SECoPError, secop_error
|
||||||
from secop.lib import formatException, mkthread, UniqueObject, generalConfig
|
from secop.lib import formatException, mkthread, UniqueObject, generalConfig
|
||||||
from secop.lib.enum import Enum
|
from secop.lib.enum import Enum
|
||||||
@ -476,10 +476,12 @@ class Module(HasAccessibles):
|
|||||||
aobj.finish()
|
aobj.finish()
|
||||||
|
|
||||||
# Modify units AFTER applying the cfgdict
|
# Modify units AFTER applying the cfgdict
|
||||||
|
mainvalue = self.parameters.get('value')
|
||||||
|
if mainvalue:
|
||||||
|
mainunit = mainvalue.datatype.unit
|
||||||
|
if mainunit:
|
||||||
for pname, pobj in self.parameters.items():
|
for pname, pobj in self.parameters.items():
|
||||||
dt = pobj.datatype
|
pobj.datatype.set_main_unit(mainunit)
|
||||||
if '$' in dt.unit:
|
|
||||||
dt.setProperty('unit', dt.unit.replace('$', self.parameters['value'].datatype.unit))
|
|
||||||
|
|
||||||
# 6) check complete configuration of * properties
|
# 6) check complete configuration of * properties
|
||||||
if not errors:
|
if not errors:
|
||||||
@ -639,7 +641,7 @@ class Module(HasAccessibles):
|
|||||||
self.pollInfo.interval = fast_interval if flag else self.pollinterval
|
self.pollInfo.interval = fast_interval if flag else self.pollinterval
|
||||||
self.pollInfo.trigger()
|
self.pollInfo.trigger()
|
||||||
|
|
||||||
def callPollFunc(self, rfunc):
|
def callPollFunc(self, rfunc, raise_com_failed=False):
|
||||||
"""call read method with proper error handling"""
|
"""call read method with proper error handling"""
|
||||||
try:
|
try:
|
||||||
rfunc()
|
rfunc()
|
||||||
@ -656,6 +658,8 @@ class Module(HasAccessibles):
|
|||||||
else:
|
else:
|
||||||
# uncatched error: this is more serious
|
# uncatched error: this is more serious
|
||||||
self.log.error('%s: %s', name, formatException())
|
self.log.error('%s: %s', name, formatException())
|
||||||
|
if raise_com_failed and isinstance(e, CommunicationFailedError):
|
||||||
|
raise
|
||||||
|
|
||||||
def __pollThread(self, modules, started_callback):
|
def __pollThread(self, modules, started_callback):
|
||||||
"""poll thread body
|
"""poll thread body
|
||||||
@ -680,7 +684,7 @@ class Module(HasAccessibles):
|
|||||||
trg.set()
|
trg.set()
|
||||||
self.registerReconnectCallback('trigger_polls', trigger_all)
|
self.registerReconnectCallback('trigger_polls', trigger_all)
|
||||||
|
|
||||||
# collect and call all read functions a first time
|
# collect all read functions
|
||||||
for mobj in modules:
|
for mobj in modules:
|
||||||
pinfo = mobj.pollInfo = PollInfo(mobj.pollinterval, self.triggerPoll)
|
pinfo = mobj.pollInfo = PollInfo(mobj.pollinterval, self.triggerPoll)
|
||||||
# trigger a poll interval change when self.pollinterval changes.
|
# trigger a poll interval change when self.pollinterval changes.
|
||||||
@ -691,7 +695,16 @@ class Module(HasAccessibles):
|
|||||||
rfunc = getattr(mobj, 'read_' + pname)
|
rfunc = getattr(mobj, 'read_' + pname)
|
||||||
if rfunc.poll:
|
if rfunc.poll:
|
||||||
pinfo.polled_parameters.append((mobj, rfunc, pobj))
|
pinfo.polled_parameters.append((mobj, rfunc, pobj))
|
||||||
mobj.callPollFunc(rfunc)
|
# call all read functions a first time
|
||||||
|
try:
|
||||||
|
for m in modules:
|
||||||
|
for mobj, rfunc, _ in m.pollInfo.polled_parameters:
|
||||||
|
mobj.callPollFunc(rfunc, raise_com_failed=True)
|
||||||
|
except CommunicationFailedError as e:
|
||||||
|
# when communication failed, probably all parameters and may be more modules are affected.
|
||||||
|
# as this would take a lot of time (summed up timeouts), we do not continue
|
||||||
|
# trying and let the server accept connections, further polls might success later
|
||||||
|
self.log.error('communication failure on startup: %s', e)
|
||||||
started_callback()
|
started_callback()
|
||||||
to_poll = ()
|
to_poll = ()
|
||||||
while True:
|
while True:
|
||||||
|
@ -129,7 +129,7 @@ class PersistentMixin(HasAccessibles):
|
|||||||
if getattr(v, 'persistent', False)}
|
if getattr(v, 'persistent', False)}
|
||||||
if data != self.persistentData:
|
if data != self.persistentData:
|
||||||
self.persistentData = data
|
self.persistentData = data
|
||||||
persistentdir = os.path.basename(self.persistentFile)
|
persistentdir = os.path.dirname(self.persistentFile)
|
||||||
tmpfile = self.persistentFile + '.tmp'
|
tmpfile = self.persistentFile + '.tmp'
|
||||||
if not os.path.isdir(persistentdir):
|
if not os.path.isdir(persistentdir):
|
||||||
os.makedirs(persistentdir, exist_ok=True)
|
os.makedirs(persistentdir, exist_ok=True)
|
||||||
|
@ -28,6 +28,9 @@ Here we support devices which fulfill the official
|
|||||||
MLZ TANGO interface for the respective device classes.
|
MLZ TANGO interface for the respective device classes.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
# pylint: disable=too-many-lines
|
||||||
|
|
||||||
|
|
||||||
import re
|
import re
|
||||||
import threading
|
import threading
|
||||||
from time import sleep
|
from time import sleep
|
||||||
@ -173,7 +176,7 @@ class PyTangoDevice(Module):
|
|||||||
tango_status_mapping = {
|
tango_status_mapping = {
|
||||||
PyTango.DevState.ON: Drivable.Status.IDLE,
|
PyTango.DevState.ON: Drivable.Status.IDLE,
|
||||||
PyTango.DevState.ALARM: Drivable.Status.WARN,
|
PyTango.DevState.ALARM: Drivable.Status.WARN,
|
||||||
PyTango.DevState.OFF: Drivable.Status.ERROR,
|
PyTango.DevState.OFF: Drivable.Status.DISABLED,
|
||||||
PyTango.DevState.FAULT: Drivable.Status.ERROR,
|
PyTango.DevState.FAULT: Drivable.Status.ERROR,
|
||||||
PyTango.DevState.MOVING: Drivable.Status.BUSY,
|
PyTango.DevState.MOVING: Drivable.Status.BUSY,
|
||||||
}
|
}
|
||||||
@ -504,6 +507,9 @@ class AnalogOutput(PyTangoDevice, Drivable):
|
|||||||
return stable and at_target
|
return stable and at_target
|
||||||
|
|
||||||
def read_status(self):
|
def read_status(self):
|
||||||
|
_st, _sts = super().read_status()
|
||||||
|
if _st == Readable.Status.DISABLED:
|
||||||
|
return _st, _sts
|
||||||
if self._isAtTarget():
|
if self._isAtTarget():
|
||||||
self._timeout = None
|
self._timeout = None
|
||||||
self._moving = False
|
self._moving = False
|
||||||
|
@ -20,11 +20,13 @@
|
|||||||
# *****************************************************************************
|
# *****************************************************************************
|
||||||
"""oxford instruments mercury IPS power supply"""
|
"""oxford instruments mercury IPS power supply"""
|
||||||
|
|
||||||
|
import time
|
||||||
from secop.core import Parameter, EnumType, FloatRange, BoolType
|
from secop.core import Parameter, EnumType, FloatRange, BoolType
|
||||||
from secop.lib.enum import Enum
|
from secop.lib.enum import Enum
|
||||||
from secop.errors import BadValueError
|
from secop.errors import BadValueError, HardwareError
|
||||||
from secop_psi.magfield import Magfield
|
from secop_psi.magfield import Magfield
|
||||||
from secop_psi.mercury import MercuryChannel, off_on, Mapped
|
from secop_psi.mercury import MercuryChannel, off_on, Mapped
|
||||||
|
from secop.lib.statemachine import Retry
|
||||||
|
|
||||||
Action = Enum(hold=0, run_to_set=1, run_to_zero=2, clamped=3)
|
Action = Enum(hold=0, run_to_set=1, run_to_zero=2, clamped=3)
|
||||||
hold_rtoz_rtos_clmp = Mapped(HOLD=Action.hold, RTOS=Action.run_to_set,
|
hold_rtoz_rtos_clmp = Mapped(HOLD=Action.hold, RTOS=Action.run_to_set,
|
||||||
@ -37,6 +39,12 @@ class Field(MercuryChannel, Magfield):
|
|||||||
setpoint = Parameter('field setpoint', FloatRange(unit='T'), default=0)
|
setpoint = Parameter('field setpoint', FloatRange(unit='T'), default=0)
|
||||||
voltage = Parameter('leads voltage', FloatRange(unit='V'), default=0)
|
voltage = Parameter('leads voltage', FloatRange(unit='V'), default=0)
|
||||||
atob = Parameter('field to amp', FloatRange(0, unit='A/T'), default=0)
|
atob = Parameter('field to amp', FloatRange(0, unit='A/T'), default=0)
|
||||||
|
I1 = Parameter('master current', FloatRange(unit='A'), default=0)
|
||||||
|
I2 = Parameter('slave 2 current', FloatRange(unit='A'), default=0)
|
||||||
|
I3 = Parameter('slave 3 current', FloatRange(unit='A'), default=0)
|
||||||
|
V1 = Parameter('master voltage', FloatRange(unit='V'), default=0)
|
||||||
|
V2 = Parameter('slave 2 voltage', FloatRange(unit='V'), default=0)
|
||||||
|
V3 = Parameter('slave 3 voltage', FloatRange(unit='V'), default=0)
|
||||||
forced_persistent_field = Parameter(
|
forced_persistent_field = Parameter(
|
||||||
'manual indication that persistent field is bad', BoolType(), readonly=False, default=False)
|
'manual indication that persistent field is bad', BoolType(), readonly=False, default=False)
|
||||||
|
|
||||||
@ -46,13 +54,17 @@ class Field(MercuryChannel, Magfield):
|
|||||||
slave_currents = None
|
slave_currents = None
|
||||||
__init = True
|
__init = True
|
||||||
|
|
||||||
|
def doPoll(self):
|
||||||
|
super().doPoll()
|
||||||
|
self.read_current()
|
||||||
|
|
||||||
def read_value(self):
|
def read_value(self):
|
||||||
self.current = self.query('PSU:SIG:FLD')
|
self.current = self.query('PSU:SIG:FLD')
|
||||||
pf = self.query('PSU:SIG:PFLD')
|
pf = self.query('PSU:SIG:PFLD')
|
||||||
if self.__init:
|
if self.__init:
|
||||||
self.__init = False
|
self.__init = False
|
||||||
self.persistent_field = pf
|
self.persistent_field = pf
|
||||||
if self.switch_heater != 0 or self._field_mismatch is None:
|
if self.switch_heater == self.switch_heater.on or self._field_mismatch is None:
|
||||||
self.forced_persistent_field = False
|
self.forced_persistent_field = False
|
||||||
self._field_mismatch = False
|
self._field_mismatch = False
|
||||||
return self.current
|
return self.current
|
||||||
@ -84,7 +96,13 @@ class Field(MercuryChannel, Magfield):
|
|||||||
return self.change('PSU:ACTN', value, hold_rtoz_rtos_clmp)
|
return self.change('PSU:ACTN', value, hold_rtoz_rtos_clmp)
|
||||||
|
|
||||||
def read_switch_heater(self):
|
def read_switch_heater(self):
|
||||||
return self.query('PSU:SIG:SWHT', off_on)
|
value = self.query('PSU:SIG:SWHT', off_on)
|
||||||
|
now = time.time()
|
||||||
|
if value != self.switch_heater:
|
||||||
|
if now < (self.switch_time[self.switch_heater] or 0) + 10:
|
||||||
|
# probably switch heater was changed, but IPS reply is not yet updated
|
||||||
|
return self.switch_heater
|
||||||
|
return value
|
||||||
|
|
||||||
def write_switch_heater(self, value):
|
def write_switch_heater(self, value):
|
||||||
return self.change('PSU:SIG:SWHT', value, off_on)
|
return self.change('PSU:SIG:SWHT', value, off_on)
|
||||||
@ -104,7 +122,11 @@ class Field(MercuryChannel, Magfield):
|
|||||||
current = self.query('PSU:SIG:CURR')
|
current = self.query('PSU:SIG:CURR')
|
||||||
for i in range(self.nslaves + 1):
|
for i in range(self.nslaves + 1):
|
||||||
if i:
|
if i:
|
||||||
self.slave_currents[i].append(self.query('DEV:PSU.M%d:PSU:SIG:CURR' % i))
|
curri = self.query('DEV:PSU.M%d:PSU:SIG:CURR' % i)
|
||||||
|
volti = self.query('DEV:PSU.M%d:PSU:SIG:VOLT' % i)
|
||||||
|
setattr(self, 'I%d' % i, curri)
|
||||||
|
setattr(self, 'V%d' % i, volti)
|
||||||
|
self.slave_currents[i].append(curri)
|
||||||
else:
|
else:
|
||||||
self.slave_currents[i].append(current)
|
self.slave_currents[i].append(current)
|
||||||
min_i = min(self.slave_currents[i])
|
min_i = min(self.slave_currents[i])
|
||||||
@ -128,12 +150,18 @@ class Field(MercuryChannel, Magfield):
|
|||||||
try:
|
try:
|
||||||
self.set_and_go(self.persistent_field)
|
self.set_and_go(self.persistent_field)
|
||||||
except (HardwareError, AssertionError):
|
except (HardwareError, AssertionError):
|
||||||
state.switch_undef = self.switch_on_time or state.now
|
state.switch_undef = self.switch_time[self.switch_heater.on] or state.now
|
||||||
return self.wait_for_switch
|
return self.wait_for_switch
|
||||||
return self.ramp_to_field
|
return self.ramp_to_field
|
||||||
|
|
||||||
|
def ramp_to_field(self, state):
|
||||||
|
if self.action != 'run_to_set':
|
||||||
|
self.status = Status.PREPARING, 'restart ramp to field'
|
||||||
|
return self.start_ramp_to_field
|
||||||
|
return super().ramp_to_field(state)
|
||||||
|
|
||||||
def wait_for_switch(self, state):
|
def wait_for_switch(self, state):
|
||||||
if self.now - self.switch_undef < self.wait_switch_on:
|
if state.now - state.switch_undef < self.wait_switch_on:
|
||||||
return Retry()
|
return Retry()
|
||||||
self.set_and_go(self.persistent_field)
|
self.set_and_go(self.persistent_field)
|
||||||
return self.ramp_to_field
|
return self.ramp_to_field
|
||||||
|
@ -44,6 +44,9 @@ Status = Enum(
|
|||||||
FINALIZING=390,
|
FINALIZING=390,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
OFF = 0
|
||||||
|
ON = 1
|
||||||
|
|
||||||
|
|
||||||
class Magfield(HasLimits, Drivable):
|
class Magfield(HasLimits, Drivable):
|
||||||
value = Parameter('magnetic field', datatype=FloatRange(unit='T'))
|
value = Parameter('magnetic field', datatype=FloatRange(unit='T'))
|
||||||
@ -52,7 +55,7 @@ class Magfield(HasLimits, Drivable):
|
|||||||
'persistent mode', EnumType(Mode), readonly=False, default=Mode.PERSISTENT)
|
'persistent mode', EnumType(Mode), readonly=False, default=Mode.PERSISTENT)
|
||||||
tolerance = Parameter(
|
tolerance = Parameter(
|
||||||
'tolerance', FloatRange(0, unit='$'), readonly=False, default=0.0002)
|
'tolerance', FloatRange(0, unit='$'), readonly=False, default=0.0002)
|
||||||
switch_heater = Parameter('switch heater', EnumType(off=0, on=1),
|
switch_heater = Parameter('switch heater', EnumType(off=OFF, on=ON),
|
||||||
readonly=False, default=0)
|
readonly=False, default=0)
|
||||||
persistent_field = Parameter(
|
persistent_field = Parameter(
|
||||||
'persistent field', FloatRange(unit='$'), readonly=False)
|
'persistent field', FloatRange(unit='$'), readonly=False)
|
||||||
@ -73,33 +76,22 @@ class Magfield(HasLimits, Drivable):
|
|||||||
# ArrayOf(TupleOf(FloatRange(unit='$'), FloatRange(unit='$/min'))), readonly=False)
|
# ArrayOf(TupleOf(FloatRange(unit='$'), FloatRange(unit='$/min'))), readonly=False)
|
||||||
# TODO: the following parameters should be changed into properties after tests
|
# TODO: the following parameters should be changed into properties after tests
|
||||||
wait_switch_on = Parameter(
|
wait_switch_on = Parameter(
|
||||||
'wait time to ensure switch is on', FloatRange(0, unit='s'), readonly=False, default=61)
|
'wait time to ensure switch is on', FloatRange(0, unit='s'), readonly=False, default=60)
|
||||||
wait_switch_off = Parameter(
|
wait_switch_off = Parameter(
|
||||||
'wait time to ensure switch is off', FloatRange(0, unit='s'), readonly=False, default=61)
|
'wait time to ensure switch is off', FloatRange(0, unit='s'), readonly=False, default=60)
|
||||||
wait_stable_leads = Parameter(
|
wait_stable_leads = Parameter(
|
||||||
'wait time to ensure current is stable', FloatRange(0, unit='s'), readonly=False, default=6)
|
'wait time to ensure current is stable', FloatRange(0, unit='s'), readonly=False, default=6)
|
||||||
wait_stable_field = Parameter(
|
wait_stable_field = Parameter(
|
||||||
'wait time to ensure field is stable', FloatRange(0, unit='s'), readonly=False, default=31)
|
'wait time to ensure field is stable', FloatRange(0, unit='s'), readonly=False, default=30)
|
||||||
persistent_limit = Parameter(
|
persistent_limit = Parameter(
|
||||||
'above this limit, lead currents are not driven to 0',
|
'above this limit, lead currents are not driven to 0',
|
||||||
FloatRange(0, unit='$'), readonly=False, default=99)
|
FloatRange(0, unit='$'), readonly=False, default=99)
|
||||||
|
|
||||||
_state = None
|
_state = None
|
||||||
__init = True
|
|
||||||
_last_target = None
|
_last_target = None
|
||||||
switch_on_time = None
|
switch_time = None, None
|
||||||
switch_off_time = None
|
|
||||||
|
|
||||||
def doPoll(self):
|
def doPoll(self):
|
||||||
if self.__init:
|
|
||||||
self.__init = False
|
|
||||||
if self.read_switch_heater() and self.mode == Mode.PERSISTENT:
|
|
||||||
self.read_value() # check for persistent field mismatch
|
|
||||||
# switch off heater from previous live or manual intervention
|
|
||||||
self.write_target(self.persistent_field)
|
|
||||||
else:
|
|
||||||
self._last_target = self.persistent_field
|
|
||||||
else:
|
|
||||||
self.read_value()
|
self.read_value()
|
||||||
self._state.cycle()
|
self._state.cycle()
|
||||||
|
|
||||||
@ -117,6 +109,19 @@ class Magfield(HasLimits, Drivable):
|
|||||||
self.registerCallbacks(self) # for update_switch_heater
|
self.registerCallbacks(self) # for update_switch_heater
|
||||||
self._state = StateMachine(logger=self.log, threaded=False, cleanup=self.cleanup_state)
|
self._state = StateMachine(logger=self.log, threaded=False, cleanup=self.cleanup_state)
|
||||||
|
|
||||||
|
def startModule(self, start_events):
|
||||||
|
start_events.queue(self.startupCheck)
|
||||||
|
super().startModule(start_events)
|
||||||
|
|
||||||
|
def startupCheck(self):
|
||||||
|
if self.read_switch_heater() and self.mode == Mode.PERSISTENT:
|
||||||
|
self.read_value() # check for persistent field mismatch
|
||||||
|
# switch off heater from previous live or manual intervention
|
||||||
|
self.write_mode(self.mode)
|
||||||
|
self.write_target(self.persistent_field)
|
||||||
|
else:
|
||||||
|
self._last_target = self.persistent_field
|
||||||
|
|
||||||
def write_target(self, target):
|
def write_target(self, target):
|
||||||
self.check_limits(target)
|
self.check_limits(target)
|
||||||
self.target = target
|
self.target = target
|
||||||
@ -185,14 +190,11 @@ class Magfield(HasLimits, Drivable):
|
|||||||
|
|
||||||
def update_switch_heater(self, value):
|
def update_switch_heater(self, value):
|
||||||
"""is called whenever switch heater was changed"""
|
"""is called whenever switch heater was changed"""
|
||||||
if value != 0:
|
switch_time = self.switch_time[value]
|
||||||
self.switch_off_time = None
|
if switch_time is None:
|
||||||
if self.switch_on_time is None:
|
switch_time = time.time()
|
||||||
self.switch_on_time = time.time()
|
self.switch_time = [None, None]
|
||||||
else:
|
self.switch_time[value] = switch_time
|
||||||
self.switch_on_time = None
|
|
||||||
if self.switch_off_time is None:
|
|
||||||
self.switch_off_time = time.time()
|
|
||||||
|
|
||||||
def start_switch_on(self, state):
|
def start_switch_on(self, state):
|
||||||
"""switch heater on"""
|
"""switch heater on"""
|
||||||
@ -213,12 +215,10 @@ class Magfield(HasLimits, Drivable):
|
|||||||
abs(self.target - self.persistent_field) <= self.tolerance): # short cut
|
abs(self.target - self.persistent_field) <= self.tolerance): # short cut
|
||||||
return self.check_switch_off
|
return self.check_switch_off
|
||||||
self.read_switch_heater()
|
self.read_switch_heater()
|
||||||
if self.switch_on_time is None:
|
if self.switch_time[ON] is None:
|
||||||
if state.now - self.switch_off_time > 10:
|
|
||||||
self.log.warning('switch turned off manually?')
|
self.log.warning('switch turned off manually?')
|
||||||
return self.start_switch_on
|
return self.start_switch_on
|
||||||
return Retry()
|
if state.now - self.switch_time[ON] < self.wait_switch_on:
|
||||||
if state.now - self.switch_on_time < self.wait_switch_on:
|
|
||||||
return Retry()
|
return Retry()
|
||||||
self._last_target = self.target
|
self._last_target = self.target
|
||||||
return self.start_ramp_to_target
|
return self.start_ramp_to_target
|
||||||
@ -279,12 +279,10 @@ class Magfield(HasLimits, Drivable):
|
|||||||
return self.start_switch_on
|
return self.start_switch_on
|
||||||
self.persistent_field = self.value
|
self.persistent_field = self.value
|
||||||
self.read_switch_heater()
|
self.read_switch_heater()
|
||||||
if self.switch_off_time is None:
|
if self.switch_time[OFF] is None:
|
||||||
if state.now - self.switch_on_time > 10:
|
|
||||||
self.log.warning('switch turned on manually?')
|
self.log.warning('switch turned on manually?')
|
||||||
return self.start_switch_off
|
return self.start_switch_off
|
||||||
return Retry()
|
if state.now - self.switch_time[OFF] < self.wait_switch_off:
|
||||||
if state.now - self.switch_off_time < self.wait_switch_off:
|
|
||||||
return Retry()
|
return Retry()
|
||||||
if abs(self.value) > self.persistent_limit:
|
if abs(self.value) > self.persistent_limit:
|
||||||
self.status = Status.IDLE, 'leads current at field, switch off'
|
self.status = Status.IDLE, 'leads current at field, switch off'
|
||||||
|
@ -345,7 +345,7 @@ class HeaterOutput(HasInput, MercuryChannel, Writable):
|
|||||||
class TemperatureLoop(TemperatureSensor, Loop, Drivable):
|
class TemperatureLoop(TemperatureSensor, Loop, Drivable):
|
||||||
channel_type = 'TEMP'
|
channel_type = 'TEMP'
|
||||||
output_module = Attached(HasInput, mandatory=False)
|
output_module = Attached(HasInput, mandatory=False)
|
||||||
ramp = Parameter('ramp rate', FloatRange(0, unit='K/min'), readonly=False)
|
ramp = Parameter('ramp rate', FloatRange(0, unit='$/min'), readonly=False)
|
||||||
enable_ramp = Parameter('enable ramp rate', BoolType(), readonly=False)
|
enable_ramp = Parameter('enable ramp rate', BoolType(), readonly=False)
|
||||||
setpoint = Parameter('working setpoint (differs from target when ramping)', FloatRange(0, unit='$'))
|
setpoint = Parameter('working setpoint (differs from target when ramping)', FloatRange(0, unit='$'))
|
||||||
tolerance = Parameter(default=0.1)
|
tolerance = Parameter(default=0.1)
|
||||||
|
@ -35,7 +35,9 @@ class PhytronIO(StringIO):
|
|||||||
identification = [('0IVR', 'MCC Minilog .*')]
|
identification = [('0IVR', 'MCC Minilog .*')]
|
||||||
|
|
||||||
def communicate(self, command):
|
def communicate(self, command):
|
||||||
for ntry in range(5, 0, -1):
|
ntry = 5
|
||||||
|
warn = None
|
||||||
|
for itry in range(ntry):
|
||||||
try:
|
try:
|
||||||
_, _, reply = super().communicate('\x02' + command).partition('\x02')
|
_, _, reply = super().communicate('\x02' + command).partition('\x02')
|
||||||
if reply[0] == '\x06': # ACK
|
if reply[0] == '\x06': # ACK
|
||||||
@ -43,9 +45,12 @@ class PhytronIO(StringIO):
|
|||||||
raise CommunicationFailedError('missing ACK %r (cmd: %r)'
|
raise CommunicationFailedError('missing ACK %r (cmd: %r)'
|
||||||
% (reply, command))
|
% (reply, command))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
if ntry == 1:
|
if itry < ntry - 1:
|
||||||
|
warn = e
|
||||||
|
else:
|
||||||
raise
|
raise
|
||||||
self.log.warning('%s - retry', e)
|
if warn:
|
||||||
|
self.log.warning('needed %d retries after %r', itry, warn)
|
||||||
return reply[1:]
|
return reply[1:]
|
||||||
|
|
||||||
|
|
||||||
|
@ -27,7 +27,8 @@ from os.path import basename, dirname, exists, join
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
from scipy.interpolate import splev, splrep # pylint: disable=import-error
|
from scipy.interpolate import splev, splrep # pylint: disable=import-error
|
||||||
|
|
||||||
from secop.core import Attached, BoolType, Parameter, Readable, StringType, FloatRange
|
from secop.core import Attached, BoolType, Parameter, Readable, StringType, \
|
||||||
|
FloatRange, Done
|
||||||
|
|
||||||
|
|
||||||
def linear(x):
|
def linear(x):
|
||||||
@ -182,7 +183,6 @@ class Sensor(Readable):
|
|||||||
|
|
||||||
description = 'a calibrated sensor value'
|
description = 'a calibrated sensor value'
|
||||||
_value_error = None
|
_value_error = None
|
||||||
enablePoll = False
|
|
||||||
|
|
||||||
def checkProperties(self):
|
def checkProperties(self):
|
||||||
if 'description' not in self.propertyValues:
|
if 'description' not in self.propertyValues:
|
||||||
@ -196,6 +196,9 @@ class Sensor(Readable):
|
|||||||
if self.description == '_':
|
if self.description == '_':
|
||||||
self.description = '%r calibrated with curve %r' % (self.rawsensor, self.calib)
|
self.description = '%r calibrated with curve %r' % (self.rawsensor, self.calib)
|
||||||
|
|
||||||
|
def doPoll(self):
|
||||||
|
self.read_status()
|
||||||
|
|
||||||
def write_calib(self, value):
|
def write_calib(self, value):
|
||||||
self._calib = CalCurve(value)
|
self._calib = CalCurve(value)
|
||||||
return value
|
return value
|
||||||
@ -221,3 +224,8 @@ class Sensor(Readable):
|
|||||||
|
|
||||||
def read_value(self):
|
def read_value(self):
|
||||||
return self._calib(self.rawsensor.read_value())
|
return self._calib(self.rawsensor.read_value())
|
||||||
|
|
||||||
|
def read_status(self):
|
||||||
|
self.update_status(self.rawsensor.status)
|
||||||
|
return Done
|
||||||
|
|
||||||
|
@ -25,7 +25,12 @@ import time
|
|||||||
import math
|
import math
|
||||||
from secop.core import Drivable, Parameter, FloatRange, Done, \
|
from secop.core import Drivable, Parameter, FloatRange, Done, \
|
||||||
Attached, Command, PersistentMixin, PersistentParam, BoolType
|
Attached, Command, PersistentMixin, PersistentParam, BoolType
|
||||||
from secop.errors import BadValueError
|
from secop.errors import BadValueError, SECoPError
|
||||||
|
from secop.lib.statemachine import Retry, StateMachine, Restart
|
||||||
|
|
||||||
|
|
||||||
|
class Error(SECoPError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class Uniax(PersistentMixin, Drivable):
|
class Uniax(PersistentMixin, Drivable):
|
||||||
@ -33,11 +38,11 @@ class Uniax(PersistentMixin, Drivable):
|
|||||||
motor = Attached()
|
motor = Attached()
|
||||||
transducer = Attached()
|
transducer = Attached()
|
||||||
limit = Parameter('abs limit of force', FloatRange(0, 190, unit='N'), readonly=False, default=150)
|
limit = Parameter('abs limit of force', FloatRange(0, 190, unit='N'), readonly=False, default=150)
|
||||||
tolerance = Parameter('force tolerance', FloatRange(0, 10, unit='N'), readonly=False, default=0.1)
|
tolerance = Parameter('force tolerance', FloatRange(0, 10, unit='N'), readonly=False, default=0.2)
|
||||||
slope = PersistentParam('spring constant', FloatRange(unit='deg/N'), readonly=False,
|
slope = PersistentParam('spring constant', FloatRange(unit='deg/N'), readonly=False,
|
||||||
default=0.5, persistent='auto')
|
default=0.5, persistent='auto')
|
||||||
pid_i = PersistentParam('integral', FloatRange(), readonly=False, default=0.5, persistent='auto')
|
pid_i = PersistentParam('integral', FloatRange(), readonly=False, default=0.5, persistent='auto')
|
||||||
filter_interval = Parameter('filter time', FloatRange(0, 60, unit='s'), readonly=False, default=1)
|
filter_interval = Parameter('filter time', FloatRange(0, 60, unit='s'), readonly=False, default=5)
|
||||||
current_step = Parameter('', FloatRange(unit='deg'), default=0)
|
current_step = Parameter('', FloatRange(unit='deg'), default=0)
|
||||||
force_offset = PersistentParam('transducer offset', FloatRange(unit='N'), readonly=False, default=0,
|
force_offset = PersistentParam('transducer offset', FloatRange(unit='N'), readonly=False, default=0,
|
||||||
initwrite=True, persistent='auto')
|
initwrite=True, persistent='auto')
|
||||||
@ -52,8 +57,11 @@ class Uniax(PersistentMixin, Drivable):
|
|||||||
default=0.2, persistent='auto')
|
default=0.2, persistent='auto')
|
||||||
low_pos = Parameter('max. position for positive forces', FloatRange(unit='deg'), readonly=False, needscfg=False)
|
low_pos = Parameter('max. position for positive forces', FloatRange(unit='deg'), readonly=False, needscfg=False)
|
||||||
high_pos = Parameter('min. position for negative forces', FloatRange(unit='deg'), readonly=False, needscfg=False)
|
high_pos = Parameter('min. position for negative forces', FloatRange(unit='deg'), readonly=False, needscfg=False)
|
||||||
|
substantial_force = Parameter('min. force change expected within motor play', FloatRange(), default=1)
|
||||||
|
motor_play = Parameter('acceptable motor play within hysteresis', FloatRange(), readonly=False, default=10)
|
||||||
|
motor_max_play = Parameter('acceptable motor play outside hysteresis', FloatRange(), readonly=False, default=90)
|
||||||
|
timeout = Parameter('driving finishes when no progress within this delay', FloatRange(), readonly=False, default=300)
|
||||||
pollinterval = 0.1
|
pollinterval = 0.1
|
||||||
fast_pollfactor = 1
|
|
||||||
|
|
||||||
_mot_target = None # for detecting manual motor manipulations
|
_mot_target = None # for detecting manual motor manipulations
|
||||||
_filter_start = 0
|
_filter_start = 0
|
||||||
@ -61,19 +69,21 @@ class Uniax(PersistentMixin, Drivable):
|
|||||||
_sum = 0
|
_sum = 0
|
||||||
_cnt_rderr = 0
|
_cnt_rderr = 0
|
||||||
_cnt_wrerr = 0
|
_cnt_wrerr = 0
|
||||||
_action = None
|
|
||||||
_last_force = 0
|
|
||||||
_expected_step = 1
|
|
||||||
_fail_cnt = 0
|
|
||||||
_in_cnt = 0
|
|
||||||
_init_action = False
|
|
||||||
_zero_pos_tol = None
|
_zero_pos_tol = None
|
||||||
_find_target = 0
|
_state = None
|
||||||
|
_force = None # raw force
|
||||||
|
|
||||||
def earlyInit(self):
|
def earlyInit(self):
|
||||||
super().earlyInit()
|
super().earlyInit()
|
||||||
self._zero_pos_tol = {}
|
self._zero_pos_tol = {}
|
||||||
self._action = self.idle
|
|
||||||
|
def initModule(self):
|
||||||
|
super().initModule()
|
||||||
|
self._state = StateMachine(logger=self.log, threaded=False, cleanup=self.cleanup)
|
||||||
|
|
||||||
|
def doPoll(self):
|
||||||
|
self.read_value()
|
||||||
|
self._state.cycle()
|
||||||
|
|
||||||
def drive_relative(self, step, ntry=3):
|
def drive_relative(self, step, ntry=3):
|
||||||
"""drive relative, try 3 times"""
|
"""drive relative, try 3 times"""
|
||||||
@ -84,7 +94,12 @@ class Uniax(PersistentMixin, Drivable):
|
|||||||
self.current_step = step
|
self.current_step = step
|
||||||
for _ in range(ntry):
|
for _ in range(ntry):
|
||||||
try:
|
try:
|
||||||
self._mot_target = self.motor.write_target(mot.value + step)
|
if abs(mot.value - mot.target) < mot.tolerance:
|
||||||
|
# make sure rounding erros do not suppress small steps
|
||||||
|
newpos = mot.target + step
|
||||||
|
else:
|
||||||
|
newpos = mot.value + step
|
||||||
|
self._mot_target = self.motor.write_target(newpos)
|
||||||
self._cnt_wrerr = max(0, self._cnt_wrerr - 1)
|
self._cnt_wrerr = max(0, self._cnt_wrerr - 1)
|
||||||
return True
|
return True
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@ -96,39 +111,52 @@ class Uniax(PersistentMixin, Drivable):
|
|||||||
self.motor.reset()
|
self.motor.reset()
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def reset_filter(self, now=0.0):
|
|
||||||
self._sum = self._cnt = 0
|
|
||||||
self._filter_start = now or time.time()
|
|
||||||
|
|
||||||
def motor_busy(self):
|
def motor_busy(self):
|
||||||
mot = self.motor
|
mot = self.motor
|
||||||
if mot.isBusy():
|
if mot.isBusy():
|
||||||
if mot.target != self._mot_target:
|
if mot.target != self._mot_target:
|
||||||
self.action = self.idle
|
raise Error('control stopped - motor moved directly')
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def next_action(self, action):
|
def read_value(self):
|
||||||
"""call next action
|
try:
|
||||||
|
self._force = force = self.transducer.read_value()
|
||||||
|
self._cnt_rderr = max(0, self._cnt_rderr - 1)
|
||||||
|
except Exception as e:
|
||||||
|
self._cnt_rderr += 1
|
||||||
|
if self._cnt_rderr > 10:
|
||||||
|
self.stop()
|
||||||
|
self.status = 'ERROR', 'too many read errors: %s' % e
|
||||||
|
self.log.error(self.status[1])
|
||||||
|
self.read_target()
|
||||||
|
return Done
|
||||||
|
|
||||||
:param action: function to be called next time
|
now = time.time()
|
||||||
:param do_now: do next action in the same cycle
|
self._sum += force
|
||||||
"""
|
self._cnt += 1
|
||||||
self._action = action
|
if now < self._filter_start + self.filter_interval:
|
||||||
self._init_action = True
|
return Done
|
||||||
self.log.info('action %r', action.__name__)
|
force = self._sum / self._cnt
|
||||||
|
self.reset_filter(now)
|
||||||
|
if abs(force) > self.limit + self.hysteresis:
|
||||||
|
self.motor.stop()
|
||||||
|
self.status = 'ERROR', 'above max limit'
|
||||||
|
self.log.error(self.status[1])
|
||||||
|
self.read_target()
|
||||||
|
return Done
|
||||||
|
if self.zero_pos(force) is None and abs(force) > self.hysteresis:
|
||||||
|
self.set_zero_pos(force, self.motor.read_value())
|
||||||
|
return force
|
||||||
|
|
||||||
def init_action(self):
|
def reset_filter(self, now=0.0):
|
||||||
"""return true when called the first time after next_action"""
|
self._sum = self._cnt = 0
|
||||||
if self._init_action:
|
self._filter_start = now or time.time()
|
||||||
self._init_action = False
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def zero_pos(self, value,):
|
def zero_pos(self, value):
|
||||||
"""get high_pos or low_pos, depending on sign of value
|
"""get high_pos or low_pos, depending on sign of value
|
||||||
|
|
||||||
:param force: when not 0, return an estimate for a good starting position
|
:param value: return an estimate for a good starting position
|
||||||
"""
|
"""
|
||||||
|
|
||||||
name = 'high_pos' if value > 0 else 'low_pos'
|
name = 'high_pos' if value > 0 else 'low_pos'
|
||||||
@ -155,207 +183,233 @@ class Uniax(PersistentMixin, Drivable):
|
|||||||
self._zero_pos_tol[name] = tol
|
self._zero_pos_tol[name] = tol
|
||||||
self.log.info('set %s = %.1f +- %.1f (@%g N)' % (name, pos, tol, force))
|
self.log.info('set %s = %.1f +- %.1f (@%g N)' % (name, pos, tol, force))
|
||||||
setattr(self, name, pos)
|
setattr(self, name, pos)
|
||||||
return pos
|
|
||||||
|
|
||||||
def find(self, force, target):
|
def cleanup(self, state):
|
||||||
"""find active (engaged) range"""
|
"""in case of error, set error status"""
|
||||||
sign = math.copysign(1, target)
|
if state.stopped: # stop or restart
|
||||||
if force * sign > self.hysteresis or force * sign > target * sign:
|
if state.stopped is Restart:
|
||||||
if self.motor_busy():
|
|
||||||
self.log.info('motor stopped - substantial force detected: %g', force)
|
|
||||||
self.motor.stop()
|
|
||||||
elif self.init_action():
|
|
||||||
self.next_action(self.adjust)
|
|
||||||
return
|
|
||||||
if abs(force) > self.hysteresis:
|
|
||||||
self.set_zero_pos(force, self.motor.read_value())
|
|
||||||
self.next_action(self.adjust)
|
|
||||||
return
|
|
||||||
if force * sign < -self.hysteresis:
|
|
||||||
self._previous_force = force
|
|
||||||
self.next_action(self.free)
|
|
||||||
return
|
|
||||||
if self.motor_busy():
|
|
||||||
if sign * self._find_target < 0: # target sign changed
|
|
||||||
self.motor.stop()
|
|
||||||
self.next_action(self.find) # restart find
|
|
||||||
return
|
return
|
||||||
|
self.status = 'IDLE', 'stopped'
|
||||||
|
self.log.warning('stopped')
|
||||||
else:
|
else:
|
||||||
self._find_target = target
|
self.status = 'ERROR', str(state.last_error)
|
||||||
zero_pos = self.zero_pos(target)
|
if isinstance(state.last_error, Error):
|
||||||
side_name = 'positive' if target > 0 else 'negative'
|
self.log.error('%s', state.last_error)
|
||||||
if not self.init_action():
|
else:
|
||||||
|
self.log.error('%r raised in state %r', str(state.last_error), state.status_string)
|
||||||
|
self.read_target() # make target invalid
|
||||||
|
self.motor.stop()
|
||||||
|
self.write_adjusting(False)
|
||||||
|
|
||||||
|
def reset_progress(self, state):
|
||||||
|
state.prev_force = self.value
|
||||||
|
state.prev_pos = self.motor.value
|
||||||
|
state.prev_time = time.time()
|
||||||
|
|
||||||
|
def check_progress(self, state):
|
||||||
|
force_step = self.target - self.value
|
||||||
|
direction = math.copysign(1, force_step)
|
||||||
|
try:
|
||||||
|
force_progress = direction * (self.value - state.prev_force)
|
||||||
|
except AttributeError: # prev_force undefined?
|
||||||
|
self.reset_progress(state)
|
||||||
|
return True
|
||||||
|
if force_progress >= self.substantial_force:
|
||||||
|
self.reset_progress(state)
|
||||||
|
else:
|
||||||
|
motor_dif = abs(self.motor.value - state.prev_pos)
|
||||||
|
if motor_dif > self.motor_play:
|
||||||
|
if motor_dif > self.motor_max_play:
|
||||||
|
raise Error('force seems not to change substantially %g %g (%g %g)' % (self.value, self.motor.value, state.prev_force, state.prev_pos))
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def adjust(self, state):
|
||||||
|
"""adjust force"""
|
||||||
|
if state.init:
|
||||||
|
state.phase = 0 # just initialized
|
||||||
|
state.in_since = 0
|
||||||
|
state.direction = math.copysign(1, self.target - self.value)
|
||||||
|
state.pid_fact = 1
|
||||||
|
if self.motor_busy():
|
||||||
|
return Retry()
|
||||||
|
self.value = self._force
|
||||||
|
force_step = self.target - self.value
|
||||||
|
if abs(force_step) < self.tolerance:
|
||||||
|
if state.in_since == 0:
|
||||||
|
state.in_since = state.now
|
||||||
|
if state.now > state.in_since + 10:
|
||||||
|
return self.within_tolerance
|
||||||
|
else:
|
||||||
|
if force_step * state.direction < 0:
|
||||||
|
if state.pid_fact == 1:
|
||||||
|
self.log.info('overshoot -> adjust with reduced pid_i')
|
||||||
|
state.pid_fact = 0.1
|
||||||
|
state.in_since = 0
|
||||||
|
if state.phase == 0:
|
||||||
|
state.phase = 1
|
||||||
|
self.reset_progress(state)
|
||||||
|
self.write_adjusting(True)
|
||||||
|
self.status = 'BUSY', 'adjusting force'
|
||||||
|
elif not self.check_progress(state):
|
||||||
|
if abs(self.value) < self.hysteresis:
|
||||||
|
if motor_dif > self.motor_play:
|
||||||
|
self.log.warning('adjusting failed - try to find zero pos')
|
||||||
|
self.set_zero_pos(self.target, None)
|
||||||
|
return self.find
|
||||||
|
elif time.time() > state.prev_time + self.timeout:
|
||||||
|
if state.phase == 1:
|
||||||
|
state.phase = 2
|
||||||
|
self.log.warning('no substantial progress since %d sec', self.timeout)
|
||||||
|
self.status = 'IDLE', 'adjusting timeout'
|
||||||
|
self.drive_relative(force_step * self.slope * self.pid_i * min(1, state.delta()) * state.pid_fact)
|
||||||
|
return Retry()
|
||||||
|
|
||||||
|
def within_tolerance(self, state):
|
||||||
|
"""within tolerance"""
|
||||||
|
if state.init:
|
||||||
|
self.status = 'IDLE', 'within tolerance'
|
||||||
|
return Retry()
|
||||||
|
if self.motor_busy():
|
||||||
|
return Retry()
|
||||||
|
force_step = self.target - self.value
|
||||||
|
if abs(force_step) < self.tolerance * 0.5:
|
||||||
|
self.current_step = 0
|
||||||
|
else:
|
||||||
|
self.check_progress(state)
|
||||||
|
self.drive_relative(force_step * self.slope * self.pid_i * min(1, state.delta()) * 0.1)
|
||||||
|
if abs(force_step) > self.tolerance:
|
||||||
|
return self.out_of_tolerance
|
||||||
|
return Retry()
|
||||||
|
|
||||||
|
def out_of_tolerance(self, state):
|
||||||
|
"""out of tolerance"""
|
||||||
|
if state.init:
|
||||||
|
self.status = 'WARN', 'out of tolerance'
|
||||||
|
state.in_since = 0
|
||||||
|
return Retry()
|
||||||
|
if self.motor_busy():
|
||||||
|
return Retry()
|
||||||
|
force_step = self.target - self._force
|
||||||
|
if abs(force_step) < self.tolerance:
|
||||||
|
if state.in_since == 0:
|
||||||
|
state.in_since = state.now
|
||||||
|
if state.now > state.in_since + 10:
|
||||||
|
return self.within_tolerance
|
||||||
|
if abs(force_step) < self.tolerance * 0.5:
|
||||||
|
return Retry()
|
||||||
|
self.check_progress(state)
|
||||||
|
self.drive_relative(force_step * self.slope * self.pid_i * min(1, state.delta()) * 0.1)
|
||||||
|
return Retry()
|
||||||
|
|
||||||
|
def find(self, state):
|
||||||
|
"""find active (engaged) range"""
|
||||||
|
if state.init:
|
||||||
|
state.prev_direction = 0 # find not yet started
|
||||||
|
self.reset_progress(state)
|
||||||
|
direction = math.copysign(1, self.target)
|
||||||
|
self.value = self._force
|
||||||
|
abs_force = self.value * direction
|
||||||
|
if abs_force > self.hysteresis or abs_force > self.target * direction:
|
||||||
|
if self.motor_busy():
|
||||||
|
self.log.info('motor stopped - substantial force detected: %g', self.value)
|
||||||
|
self.motor.stop()
|
||||||
|
elif state.prev_direction == 0:
|
||||||
|
return self.adjust
|
||||||
|
if abs_force > self.hysteresis:
|
||||||
|
self.set_zero_pos(self.value, self.motor.read_value())
|
||||||
|
return self.adjust
|
||||||
|
if abs_force < -self.hysteresis:
|
||||||
|
state.force_before_free = self.value
|
||||||
|
return self.free
|
||||||
|
if self.motor_busy():
|
||||||
|
if direction == -state.prev_direction: # target direction changed
|
||||||
|
self.motor.stop()
|
||||||
|
state.init_find = True # restart find
|
||||||
|
return Retry()
|
||||||
|
zero_pos = self.zero_pos(self.target)
|
||||||
|
if state.prev_direction: # find already started
|
||||||
if abs(self.motor.target - self.motor.value) > self.motor.tolerance:
|
if abs(self.motor.target - self.motor.value) > self.motor.tolerance:
|
||||||
# no success on last find try, try short and strong step
|
# no success on last find try, try short and strong step
|
||||||
self.write_adjusting(True)
|
self.write_adjusting(True)
|
||||||
self.log.info('one step to %g', self.motor.value + self.safe_step)
|
self.log.info('one step to %g', self.motor.value + self.safe_step)
|
||||||
self.drive_relative(sign * self.safe_step)
|
self.drive_relative(direction * self.safe_step)
|
||||||
return
|
return Retry()
|
||||||
|
else:
|
||||||
|
state.prev_direction = math.copysign(1, self.target)
|
||||||
|
side_name = 'negative' if direction == -1 else 'positive'
|
||||||
if zero_pos is not None:
|
if zero_pos is not None:
|
||||||
self.status = 'BUSY', 'change to %s side' % side_name
|
self.status = 'BUSY', 'change to %s side' % side_name
|
||||||
zero_pos += sign * (self.hysteresis * self.slope - self.motor.tolerance)
|
zero_pos += direction * (self.hysteresis * self.slope - self.motor.tolerance)
|
||||||
if (self.motor.value - zero_pos) * sign < -self.motor.tolerance:
|
if (self.motor.value - zero_pos) * direction < -self.motor.tolerance:
|
||||||
self.write_adjusting(False)
|
self.write_adjusting(False)
|
||||||
self.log.info('change side to %g', zero_pos)
|
self.log.info('change side to %g', zero_pos)
|
||||||
self.drive_relative(zero_pos - self.motor.value)
|
self.drive_relative(zero_pos - self.motor.value)
|
||||||
return
|
return Retry()
|
||||||
# we are already at or beyond zero_pos
|
# we are already at or beyond zero_pos
|
||||||
self.next_action(self.adjust)
|
return self.adjust
|
||||||
return
|
|
||||||
self.write_adjusting(False)
|
self.write_adjusting(False)
|
||||||
self.status = 'BUSY', 'find %s side' % side_name
|
self.status = 'BUSY', 'find %s side' % side_name
|
||||||
self.log.info('one turn to %g', self.motor.value + sign * 360)
|
self.log.info('one turn to %g', self.motor.value + direction * 360)
|
||||||
self.drive_relative(sign * 360)
|
self.drive_relative(direction * 360)
|
||||||
|
return Retry()
|
||||||
|
|
||||||
def free(self, force, target):
|
def free(self, state):
|
||||||
"""free from high force at other end"""
|
"""free from high force at other end"""
|
||||||
|
if state.init:
|
||||||
|
state.free_way = None
|
||||||
|
self.reset_progress(state)
|
||||||
if self.motor_busy():
|
if self.motor_busy():
|
||||||
return
|
return Retry()
|
||||||
if abs(force) > abs(self._previous_force) + self.tolerance:
|
self.value = self._force
|
||||||
self.stop()
|
if abs(self.value) > abs(state.force_before_free) + self.hysteresis:
|
||||||
self.status = 'ERROR', 'force increase while freeing'
|
raise Error('force increase while freeing')
|
||||||
self.log.error(self.status[1])
|
if abs(self.value) < self.hysteresis:
|
||||||
return
|
return self.find
|
||||||
if abs(force) < self.hysteresis:
|
if state.free_way is None:
|
||||||
self.next_action(self.find)
|
state.free_way = 0
|
||||||
return
|
self.log.info('free from high force %g', self.value)
|
||||||
if self.init_action():
|
|
||||||
self._free_way = 0
|
|
||||||
self.log.info('free from high force %g', force)
|
|
||||||
self.write_adjusting(True)
|
self.write_adjusting(True)
|
||||||
sign = math.copysign(1, target)
|
direction = math.copysign(1, self.target)
|
||||||
if self._free_way > (abs(self._previous_force) + self.hysteresis) * self.slope:
|
if state.free_way > abs(state.force_before_free + self.hysteresis) * self.slope + self.motor_max_play:
|
||||||
self.stop()
|
raise Error('freeing failed')
|
||||||
self.status = 'ERROR', 'freeing failed'
|
state.free_way += self.safe_step
|
||||||
self.log.error(self.status[1])
|
self.drive_relative(direction * self.safe_step)
|
||||||
return
|
return Retry()
|
||||||
self._free_way += self.safe_step
|
|
||||||
self.drive_relative(sign * self.safe_step)
|
|
||||||
|
|
||||||
def within_tolerance(self, force, target):
|
|
||||||
"""within tolerance"""
|
|
||||||
if self.motor_busy():
|
|
||||||
return
|
|
||||||
if abs(target - force) > self.tolerance:
|
|
||||||
self.next_action(self.adjust)
|
|
||||||
elif self.init_action():
|
|
||||||
self.status = 'IDLE', 'within tolerance'
|
|
||||||
|
|
||||||
def adjust(self, force, target):
|
|
||||||
"""adjust force"""
|
|
||||||
if self.motor_busy():
|
|
||||||
return
|
|
||||||
if abs(target - force) < self.tolerance:
|
|
||||||
self._in_cnt += 1
|
|
||||||
if self._in_cnt >= 3:
|
|
||||||
self.next_action(self.within_tolerance)
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
self._in_cnt = 0
|
|
||||||
if self.init_action():
|
|
||||||
self._fail_cnt = 0
|
|
||||||
self.write_adjusting(True)
|
|
||||||
self.status = 'BUSY', 'adjusting force'
|
|
||||||
elif not self._filtered:
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
force_step = force - self._last_force
|
|
||||||
if self._expected_step:
|
|
||||||
# compare detected / expected step
|
|
||||||
q = force_step / self._expected_step
|
|
||||||
if q < 0.1:
|
|
||||||
self._fail_cnt += 1
|
|
||||||
elif q > 0.5:
|
|
||||||
self._fail_cnt = max(0, self._fail_cnt - 1)
|
|
||||||
if self._fail_cnt >= 10:
|
|
||||||
if force < self.hysteresis:
|
|
||||||
self.log.warning('adjusting failed - try to find zero pos')
|
|
||||||
self.set_zero_pos(target, None)
|
|
||||||
self.next_action(self.find)
|
|
||||||
elif self._fail_cnt > 20:
|
|
||||||
self.stop()
|
|
||||||
self.status = 'ERROR', 'force seems not to change substantially'
|
|
||||||
self.log.error(self.status[1])
|
|
||||||
return
|
|
||||||
self._last_force = force
|
|
||||||
force_step = (target - force) * self.pid_i
|
|
||||||
if abs(target - force) < self.tolerance * 0.5:
|
|
||||||
self._expected_step = 0
|
|
||||||
return
|
|
||||||
self._expected_step = force_step
|
|
||||||
step = force_step * self.slope
|
|
||||||
self.drive_relative(step)
|
|
||||||
|
|
||||||
def idle(self, *args):
|
|
||||||
if self.init_action():
|
|
||||||
self.write_adjusting(False)
|
|
||||||
if self.status[0] == 'BUSY':
|
|
||||||
self.status = 'IDLE', 'stopped'
|
|
||||||
|
|
||||||
def read_value(self):
|
|
||||||
try:
|
|
||||||
force = self.transducer.read_value()
|
|
||||||
self._cnt_rderr = max(0, self._cnt_rderr - 1)
|
|
||||||
except Exception as e:
|
|
||||||
self._cnt_rderr += 1
|
|
||||||
if self._cnt_rderr > 10:
|
|
||||||
self.stop()
|
|
||||||
self.status = 'ERROR', 'too many read errors: %s' % e
|
|
||||||
self.log.error(self.status[1])
|
|
||||||
return Done
|
|
||||||
|
|
||||||
now = time.time()
|
|
||||||
if self.motor_busy():
|
|
||||||
# do not filter while driving
|
|
||||||
self.value = force
|
|
||||||
self.reset_filter()
|
|
||||||
self._filtered = False
|
|
||||||
else:
|
|
||||||
self._sum += force
|
|
||||||
self._cnt += 1
|
|
||||||
if now < self._filter_start + self.filter_interval:
|
|
||||||
return Done
|
|
||||||
force = self._sum / self._cnt
|
|
||||||
self.value = force
|
|
||||||
self.reset_filter(now)
|
|
||||||
self._filtered = True
|
|
||||||
if abs(force) > self.limit + self.hysteresis:
|
|
||||||
self.status = 'ERROR', 'above max limit'
|
|
||||||
self.log.error(self.status[1])
|
|
||||||
return Done
|
|
||||||
if self.zero_pos(force) is None and abs(force) > self.hysteresis and self._filtered:
|
|
||||||
self.set_zero_pos(force, self.motor.read_value())
|
|
||||||
self._action(self.value, self.target)
|
|
||||||
return Done
|
|
||||||
|
|
||||||
def write_target(self, target):
|
def write_target(self, target):
|
||||||
if abs(target) > self.limit:
|
if abs(target) > self.limit:
|
||||||
raise BadValueError('force above limit')
|
raise BadValueError('force above limit')
|
||||||
if abs(target - self.value) <= self.tolerance:
|
if abs(target - self.value) <= self.tolerance:
|
||||||
if self.isBusy():
|
if not self.isBusy():
|
||||||
self.stop()
|
|
||||||
self.next_action(self.within_tolerance)
|
|
||||||
else:
|
|
||||||
self.status = 'IDLE', 'already at target'
|
self.status = 'IDLE', 'already at target'
|
||||||
self.next_action(self.within_tolerance)
|
self._state.start(self.within_tolerance)
|
||||||
return target
|
return target
|
||||||
self.log.info('new target %g', target)
|
self.log.info('new target %g', target)
|
||||||
self._cnt_rderr = 0
|
self._cnt_rderr = 0
|
||||||
self._cnt_wrerr = 0
|
self._cnt_wrerr = 0
|
||||||
self.status = 'BUSY', 'changed target'
|
self.status = 'BUSY', 'changed target'
|
||||||
|
self.target = target
|
||||||
if self.value * math.copysign(1, target) > self.hysteresis:
|
if self.value * math.copysign(1, target) > self.hysteresis:
|
||||||
self.next_action(self.adjust)
|
self._state.start(self.adjust)
|
||||||
else:
|
else:
|
||||||
self.next_action(self.find)
|
self._state.start(self.find)
|
||||||
return target
|
return Done
|
||||||
|
|
||||||
|
def read_target(self):
|
||||||
|
if self._state.state is None:
|
||||||
|
if self.status[1]:
|
||||||
|
raise Error(self.status[1])
|
||||||
|
raise Error('inactive')
|
||||||
|
return self.target
|
||||||
|
|
||||||
@Command()
|
@Command()
|
||||||
def stop(self):
|
def stop(self):
|
||||||
self._action = self.idle
|
|
||||||
if self.motor.isBusy():
|
if self.motor.isBusy():
|
||||||
self.log.info('stop motor')
|
self.log.info('stop motor')
|
||||||
self.motor.stop()
|
self.motor.stop()
|
||||||
self.next_action(self.idle)
|
self.status = 'IDLE', 'stopped'
|
||||||
|
self._state.stop()
|
||||||
|
|
||||||
def write_force_offset(self, value):
|
def write_force_offset(self, value):
|
||||||
self.force_offset = value
|
self.force_offset = value
|
||||||
|
Loading…
x
Reference in New Issue
Block a user