Merge branch 'wip' of gitlab.psi.ch-samenv:samenv/frappy into wip
This commit is contained in:
commit
19f965bced
18
cfg/addons/camea-be-filter_cfg.py
Normal file
18
cfg/addons/camea-be-filter_cfg.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
Node('cfg/sea/camea-be-filter.cfg',
|
||||||
|
'Camea Be-Filter',
|
||||||
|
interface='5000',
|
||||||
|
name='camea-be-filter',
|
||||||
|
)
|
||||||
|
|
||||||
|
Mod('sea_addons',
|
||||||
|
'secop_psi.sea.SeaClient',
|
||||||
|
'addons sea connection for camea-be-filter.addon',
|
||||||
|
config='camea-be-filter.addon',
|
||||||
|
service='addons',
|
||||||
|
)
|
||||||
|
|
||||||
|
Mod('t_be_filter',
|
||||||
|
'secop_psi.sea.SeaReadable',
|
||||||
|
io='sea_addons',
|
||||||
|
sea_object='t_be_filter',
|
||||||
|
)
|
@ -58,6 +58,12 @@ Mod('hemot',
|
|||||||
sea_object='hemot',
|
sea_object='hemot',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
Mod('nvflow',
|
||||||
|
'frappy_psi.sea.SeaReadable', '',
|
||||||
|
io='sea_main',
|
||||||
|
sea_object='nvflow',
|
||||||
|
)
|
||||||
|
|
||||||
Mod('table',
|
Mod('table',
|
||||||
'frappy_psi.sea.SeaReadable', '',
|
'frappy_psi.sea.SeaReadable', '',
|
||||||
io='sea_main',
|
io='sea_main',
|
||||||
|
@ -237,10 +237,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": "", "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", "visibility": 3},
|
||||||
{"path": "auto", "type": "bool", "readonly": false, "cmd": "hepump auto", "visibility": 3},
|
{"path": "auto", "type": "bool", "readonly": false, "cmd": "hepump auto", "visibility": 3},
|
||||||
{"path": "valve", "type": "enum", "enum": {"closed": 0, "closing": 1, "opening": 2, "opened": 3, "undefined": 4}, "readonly": false, "cmd": "hepump valve", "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", "visibility": 3},
|
||||||
{"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"}]},
|
||||||
@ -278,6 +278,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"}]},
|
||||||
|
|
||||||
"table": {"base": "/table", "params": [
|
"table": {"base": "/table", "params": [
|
||||||
{"path": "", "type": "none", "kids": 17},
|
{"path": "", "type": "none", "kids": 17},
|
||||||
{"path": "send", "type": "text", "readonly": false, "cmd": "table send", "visibility": 3},
|
{"path": "send", "type": "text", "readonly": false, "cmd": "table send", "visibility": 3},
|
||||||
|
@ -7,7 +7,7 @@ Mod('triton',
|
|||||||
'frappy_psi.mercury.IO',
|
'frappy_psi.mercury.IO',
|
||||||
'connection to triton software',
|
'connection to triton software',
|
||||||
uri='tcp://linse-dil5:33576',
|
uri='tcp://linse-dil5:33576',
|
||||||
timeout=5.0,
|
timeout=25.0,
|
||||||
)
|
)
|
||||||
|
|
||||||
Mod('ts',
|
Mod('ts',
|
||||||
@ -23,6 +23,7 @@ Mod('htr_mix',
|
|||||||
'mix. chamber heater',
|
'mix. chamber heater',
|
||||||
slot='H1,T5',
|
slot='H1,T5',
|
||||||
io='triton',
|
io='triton',
|
||||||
|
resistivity = 100
|
||||||
)
|
)
|
||||||
|
|
||||||
Mod('htr_sorb',
|
Mod('htr_sorb',
|
||||||
|
@ -8,7 +8,24 @@ what the framwork does for you.
|
|||||||
Startup
|
Startup
|
||||||
.......
|
.......
|
||||||
|
|
||||||
TODO: describe startup: init methods, first polls
|
On startup several methods are called. First :meth:`earlyInit` is called on all modules.
|
||||||
|
Use this to initialize attributes independent of other modules, if you can not initialize
|
||||||
|
as a class attribute, for example for mutable attributes.
|
||||||
|
|
||||||
|
Then :meth:`initModule` is called for all modules.
|
||||||
|
Use it to initialize things related to other modules, for example registering callbacks.
|
||||||
|
|
||||||
|
After this, :meth:`startModule` is called with a callback function argument.
|
||||||
|
:func:`frappy.modules.Module.startModule` starts the poller thread, calling
|
||||||
|
:meth:`writeInitParams` for writing initial parameters to hardware, followed
|
||||||
|
by :meth:`initialReads`. The latter is meant for reading values from hardware,
|
||||||
|
which are not polled continuously. Then all parameters configured for poll are polled
|
||||||
|
by calling the corresponding read_*() method. The end of this last initialisation
|
||||||
|
step is indicated to the server by the callback function.
|
||||||
|
After this, the poller thread starts regular polling, see next section.
|
||||||
|
|
||||||
|
When overriding one of above methods, do not forget to super call.
|
||||||
|
|
||||||
|
|
||||||
.. _polling:
|
.. _polling:
|
||||||
|
|
||||||
|
@ -95,5 +95,23 @@ Example code:
|
|||||||
return self.read_target() # return the read back value
|
return self.read_target() # return the read back value
|
||||||
|
|
||||||
|
|
||||||
|
Parameter Initialisation
|
||||||
|
------------------------
|
||||||
|
|
||||||
|
Initial values of parameters might be given by several different sources:
|
||||||
|
|
||||||
|
1) value argument of a Parameter declaration
|
||||||
|
2) read from HW
|
||||||
|
3) read from persistent data file
|
||||||
|
4) value given in config file
|
||||||
|
|
||||||
|
For (2) the programmer might decide for any parameter to poll it regularely from the
|
||||||
|
hardware. In this case changes from an other input, for example a keyboard or other
|
||||||
|
interface of the connected devices would be updated continuously in Frappy.
|
||||||
|
If there is no such other input, or if the programmer decides that such other
|
||||||
|
data sources are not to be considered, the hardware parameter might be read in just
|
||||||
|
once on startup, :func:`frappy.modules.Module.initialReads` may be overriden.
|
||||||
|
This method is called once on startup, before the regular polls start.
|
||||||
|
|
||||||
|
|
||||||
.. TODO: io, state machine, persistent parameters, rwhandler, datatypes, features, commands, proxies
|
.. TODO: io, state machine, persistent parameters, rwhandler, datatypes, features, commands, proxies
|
||||||
|
@ -12,7 +12,7 @@ Module Base Classes
|
|||||||
...................
|
...................
|
||||||
|
|
||||||
.. autoclass:: frappy.modules.Module
|
.. autoclass:: frappy.modules.Module
|
||||||
:members: earlyInit, initModule, startModule
|
:members: earlyInit, initModule, startModule, initialReads
|
||||||
|
|
||||||
.. autoclass:: frappy.modules.Readable
|
.. autoclass:: frappy.modules.Readable
|
||||||
:members: Status
|
:members: Status
|
||||||
|
@ -125,7 +125,7 @@ class Config(dict):
|
|||||||
continue
|
continue
|
||||||
if name not in self.module_names:
|
if name not in self.module_names:
|
||||||
self.module_names.add(name)
|
self.module_names.add(name)
|
||||||
self.modules.append(mod)
|
self[name] = mod
|
||||||
|
|
||||||
|
|
||||||
def process_file(filename, log):
|
def process_file(filename, log):
|
||||||
|
@ -1163,9 +1163,9 @@ class ValueType(DataType):
|
|||||||
|
|
||||||
The optional (callable) validator can be used to restrict values to a
|
The optional (callable) validator can be used to restrict values to a
|
||||||
certain type.
|
certain type.
|
||||||
For example using `ValueType(dict)` would ensure only values that can be
|
For example using ``ValueType(dict)`` would ensure only values that can be
|
||||||
turned into a dictionary can be used in this instance, as the conversion
|
turned into a dictionary can be used in this instance, as the conversion
|
||||||
`dict(value)` is called for validation.
|
``dict(value)`` is called for validation.
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
The validator must either accept a value by returning it or the converted value,
|
The validator must either accept a value by returning it or the converted value,
|
||||||
|
@ -63,12 +63,15 @@ class SECoPError(RuntimeError):
|
|||||||
"""format with info about raising methods
|
"""format with info about raising methods
|
||||||
|
|
||||||
:param stripped: strip last method.
|
:param stripped: strip last method.
|
||||||
Use stripped=True (or str()) for the following cases, as the last method can be derived from the context:
|
|
||||||
- stored in pobj.readerror: read_<pobj.name>
|
|
||||||
- error message from a change command: write_<pname>
|
|
||||||
- error message from a read command: read_<pname>
|
|
||||||
Use stripped=False for the log file, as the related parameter is not known
|
|
||||||
:return: the formatted error message
|
:return: the formatted error message
|
||||||
|
|
||||||
|
Use stripped=True (or str()) for the following cases, as the last method can be derived from the context:
|
||||||
|
|
||||||
|
- stored in pobj.readerror: read_<pobj.name>
|
||||||
|
- error message from a change command: write_<pname>
|
||||||
|
- error message from a read command: read_<pname>
|
||||||
|
|
||||||
|
Use stripped=False for the log file, as the related parameter is not known
|
||||||
"""
|
"""
|
||||||
mlist = self.raising_methods
|
mlist = self.raising_methods
|
||||||
if mlist and stripped:
|
if mlist and stripped:
|
||||||
|
80
frappy/io.py
80
frappy/io.py
@ -133,6 +133,14 @@ class IOBase(Communicator):
|
|||||||
self._lock = threading.RLock()
|
self._lock = threading.RLock()
|
||||||
|
|
||||||
def connectStart(self):
|
def connectStart(self):
|
||||||
|
if not self.is_connected:
|
||||||
|
uri = self.uri
|
||||||
|
self._conn = AsynConn(uri, self._eol_read,
|
||||||
|
default_settings=self.default_settings)
|
||||||
|
self.is_connected = True
|
||||||
|
self.checkHWIdent()
|
||||||
|
|
||||||
|
def checkHWIdent(self):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def closeConnection(self):
|
def closeConnection(self):
|
||||||
@ -218,12 +226,19 @@ class StringIO(IOBase):
|
|||||||
default='\n', settable=True)
|
default='\n', settable=True)
|
||||||
encoding = Property('used encoding', datatype=StringType(),
|
encoding = Property('used encoding', datatype=StringType(),
|
||||||
default='ascii', settable=True)
|
default='ascii', settable=True)
|
||||||
identification = Property('''
|
identification = Property(
|
||||||
identification
|
'''identification
|
||||||
|
|
||||||
a list of tuples with commands and expected responses as regexp,
|
a list of tuples with commands and expected responses as regexp,
|
||||||
to be sent on connect''',
|
to be sent on connect''',
|
||||||
datatype=ArrayOf(TupleOf(StringType(), StringType())), default=[], export=False)
|
datatype=ArrayOf(TupleOf(StringType(), StringType())),
|
||||||
|
default=[], export=False)
|
||||||
|
retry_first_idn = Property(
|
||||||
|
'''retry first identification message
|
||||||
|
|
||||||
|
a flag to indicate whether the first message should be resent once to
|
||||||
|
avoid data that may still be in the buffer to garble the message''',
|
||||||
|
datatype=BoolType(), default=False)
|
||||||
|
|
||||||
def _convert_eol(self, value):
|
def _convert_eol(self, value):
|
||||||
if isinstance(value, str):
|
if isinstance(value, str):
|
||||||
@ -248,16 +263,27 @@ class StringIO(IOBase):
|
|||||||
raise ValueError('end_of_line for read must not be empty')
|
raise ValueError('end_of_line for read must not be empty')
|
||||||
self._eol_write = self._convert_eol(eol[-1])
|
self._eol_write = self._convert_eol(eol[-1])
|
||||||
|
|
||||||
def connectStart(self):
|
def checkHWIdent(self):
|
||||||
if not self.is_connected:
|
if not self.identification:
|
||||||
uri = self.uri
|
return
|
||||||
self._conn = AsynConn(uri, self._eol_read, default_settings=self.default_settings)
|
idents = iter(self.identification)
|
||||||
self.is_connected = True
|
command, regexp = next(idents)
|
||||||
for command, regexp in self.identification:
|
reply = self.communicate(command)
|
||||||
reply = self.communicate(command)
|
if not re.match(regexp, reply):
|
||||||
if not re.match(regexp, reply):
|
if self.retry_first_idn:
|
||||||
self.closeConnection()
|
self.log.debug('first ident command not successful.'
|
||||||
raise CommunicationFailedError(f'bad response: {reply} does not match {regexp}')
|
' retrying in case of garbage data.')
|
||||||
|
idents = iter(self.identification)
|
||||||
|
else:
|
||||||
|
self.closeConnection()
|
||||||
|
raise CommunicationFailedError(f'bad response: {reply!r}'
|
||||||
|
f' does not match {regexp!r}')
|
||||||
|
for command, regexp in idents:
|
||||||
|
reply = self.communicate(command)
|
||||||
|
if not re.match(regexp, reply):
|
||||||
|
self.closeConnection()
|
||||||
|
raise CommunicationFailedError(f'bad response: {reply!r}'
|
||||||
|
f' does not match {regexp!r}')
|
||||||
|
|
||||||
@Command(StringType(), result=StringType())
|
@Command(StringType(), result=StringType())
|
||||||
def communicate(self, command):
|
def communicate(self, command):
|
||||||
@ -356,19 +382,19 @@ class BytesIO(IOBase):
|
|||||||
- a two digit hexadecimal number (byte value)
|
- a two digit hexadecimal number (byte value)
|
||||||
- a character
|
- a character
|
||||||
- ?? indicating ignored bytes in responses
|
- ?? indicating ignored bytes in responses
|
||||||
""", datatype=ArrayOf(TupleOf(StringType(), StringType())), default=[], export=False)
|
""", datatype=ArrayOf(TupleOf(StringType(), StringType())),
|
||||||
|
default=[], export=False)
|
||||||
|
|
||||||
def connectStart(self):
|
_eol_read = b''
|
||||||
if not self.is_connected:
|
|
||||||
uri = self.uri
|
def checkHWIdent(self):
|
||||||
self._conn = AsynConn(uri, b'', default_settings=self.default_settings)
|
for request, expected in self.identification:
|
||||||
self.is_connected = True
|
replylen, replypat = make_regexp(expected)
|
||||||
for request, expected in self.identification:
|
reply = self.communicate(make_bytes(request), replylen)
|
||||||
replylen, replypat = make_regexp(expected)
|
if not replypat.match(reply):
|
||||||
reply = self.communicate(make_bytes(request), replylen)
|
self.closeConnection()
|
||||||
if not replypat.match(reply):
|
raise CommunicationFailedError(f'bad response: {reply!r}'
|
||||||
self.closeConnection()
|
' does not match {expected!r}')
|
||||||
raise CommunicationFailedError(f'bad response: {reply!r} does not match {expected!r}')
|
|
||||||
|
|
||||||
@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
|
||||||
|
@ -53,7 +53,7 @@ class HasControlledBy:
|
|||||||
to be called from the write_target method
|
to be called from the write_target method
|
||||||
"""
|
"""
|
||||||
if self.controlled_by:
|
if self.controlled_by:
|
||||||
self.controlled_by = 0
|
self.controlled_by = 0 # self
|
||||||
for deactivate_control in self.inputCallbacks.values():
|
for deactivate_control in self.inputCallbacks.values():
|
||||||
deactivate_control(self.name)
|
deactivate_control(self.name)
|
||||||
|
|
||||||
@ -74,6 +74,10 @@ class HasOutputModule:
|
|||||||
if self.output_module:
|
if self.output_module:
|
||||||
self.output_module.register_input(self.name, self.deactivate_control)
|
self.output_module.register_input(self.name, self.deactivate_control)
|
||||||
|
|
||||||
|
def set_control_active(self, active):
|
||||||
|
"""to be overridden for switching hw control"""
|
||||||
|
self.control_active = active
|
||||||
|
|
||||||
def activate_control(self):
|
def activate_control(self):
|
||||||
"""method to switch control_active on
|
"""method to switch control_active on
|
||||||
|
|
||||||
@ -85,10 +89,10 @@ class HasOutputModule:
|
|||||||
if name != self.name:
|
if name != self.name:
|
||||||
deactivate_control(self.name)
|
deactivate_control(self.name)
|
||||||
out.controlled_by = self.name
|
out.controlled_by = self.name
|
||||||
self.control_active = True
|
self.set_control_active(True)
|
||||||
|
|
||||||
def deactivate_control(self, source):
|
def deactivate_control(self, source=None):
|
||||||
"""called when an other module takes over control"""
|
"""called when an other module takes over control"""
|
||||||
if self.control_active:
|
if self.control_active:
|
||||||
self.control_active = False
|
self.set_control_active(False)
|
||||||
self.log.warning(f'switched to manual mode by {source}')
|
self.log.warning(f'switched to manual mode by {source or self.name}')
|
||||||
|
@ -629,6 +629,16 @@ class Module(HasAccessibles):
|
|||||||
mkthread(self.__pollThread, self.polledModules, start_events.get_trigger())
|
mkthread(self.__pollThread, self.polledModules, start_events.get_trigger())
|
||||||
self.startModuleDone = True
|
self.startModuleDone = True
|
||||||
|
|
||||||
|
def initialReads(self):
|
||||||
|
"""initial reads to be done
|
||||||
|
|
||||||
|
override to read initial values from HW, when it is not desired
|
||||||
|
to poll them afterwards
|
||||||
|
|
||||||
|
called from the poll thread, after writeInitParams but before
|
||||||
|
all parameters are polled once
|
||||||
|
"""
|
||||||
|
|
||||||
def doPoll(self):
|
def doPoll(self):
|
||||||
"""polls important parameters like value and status
|
"""polls important parameters like value and status
|
||||||
|
|
||||||
@ -678,15 +688,10 @@ class Module(HasAccessibles):
|
|||||||
|
|
||||||
before polling, parameters which need hardware initialisation are written
|
before polling, parameters which need hardware initialisation are written
|
||||||
"""
|
"""
|
||||||
for mobj in modules:
|
polled_modules = [m for m in modules if m.enablePoll]
|
||||||
mobj.writeInitParams()
|
|
||||||
modules = [m for m in modules if m.enablePoll]
|
|
||||||
if not modules: # no polls needed - exit thread
|
|
||||||
started_callback()
|
|
||||||
return
|
|
||||||
if hasattr(self, 'registerReconnectCallback'):
|
if hasattr(self, 'registerReconnectCallback'):
|
||||||
# self is a communicator supporting reconnections
|
# self is a communicator supporting reconnections
|
||||||
def trigger_all(trg=self.triggerPoll, polled_modules=modules):
|
def trigger_all(trg=self.triggerPoll, polled_modules=polled_modules):
|
||||||
for m in polled_modules:
|
for m in polled_modules:
|
||||||
m.pollInfo.last_main = 0
|
m.pollInfo.last_main = 0
|
||||||
m.pollInfo.last_slow = 0
|
m.pollInfo.last_slow = 0
|
||||||
@ -694,7 +699,7 @@ class Module(HasAccessibles):
|
|||||||
self.registerReconnectCallback('trigger_polls', trigger_all)
|
self.registerReconnectCallback('trigger_polls', trigger_all)
|
||||||
|
|
||||||
# collect all read functions
|
# collect all read functions
|
||||||
for mobj in modules:
|
for mobj in polled_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.
|
||||||
if 'pollinterval' in mobj.valueCallbacks:
|
if 'pollinterval' in mobj.valueCallbacks:
|
||||||
@ -704,17 +709,32 @@ 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))
|
||||||
# call all read functions a first time
|
while True:
|
||||||
try:
|
try:
|
||||||
for m in modules:
|
for mobj in modules:
|
||||||
for mobj, rfunc, _ in m.pollInfo.polled_parameters:
|
# TODO when needed: here we might add a call to a method :meth:`beforeWriteInit`
|
||||||
mobj.callPollFunc(rfunc, raise_com_failed=True)
|
mobj.writeInitParams()
|
||||||
except CommunicationFailedError as e:
|
mobj.initialReads()
|
||||||
# when communication failed, probably all parameters and may be more modules are affected.
|
# call all read functions a first time
|
||||||
# as this would take a lot of time (summed up timeouts), we do not continue
|
for m in polled_modules:
|
||||||
# trying and let the server accept connections, further polls might success later
|
for mobj, rfunc, _ in m.pollInfo.polled_parameters:
|
||||||
self.log.error('communication failure on startup: %s', e)
|
mobj.callPollFunc(rfunc, raise_com_failed=True)
|
||||||
started_callback()
|
# TODO when needed: here we might add calls to a method :meth:`afterInitPolls`
|
||||||
|
break
|
||||||
|
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
|
||||||
|
if started_callback:
|
||||||
|
self.log.error('communication failure on startup: %s', e)
|
||||||
|
started_callback()
|
||||||
|
started_callback = None
|
||||||
|
self.triggerPoll.wait(0.1) # wait for reconnection or max 10 sec.
|
||||||
|
break
|
||||||
|
if started_callback:
|
||||||
|
started_callback()
|
||||||
|
if not polled_modules: # no polls needed - exit thread
|
||||||
|
return
|
||||||
to_poll = ()
|
to_poll = ()
|
||||||
while True:
|
while True:
|
||||||
now = time.time()
|
now = time.time()
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# *****************************************************************************
|
# *****************************************************************************
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify it under
|
# This program is free software; you can redistribute it and/or modify it under
|
||||||
@ -257,12 +256,12 @@ class BasePyTangoDevice:
|
|||||||
Wraps command execution and attribute operations of the given
|
Wraps command execution and attribute operations of the given
|
||||||
device with logging and exception mapping.
|
device with logging and exception mapping.
|
||||||
"""
|
"""
|
||||||
dev.command_inout = self._applyGuardToFunc(dev.command_inout)
|
dev.__dict__['command_inout'] = self._applyGuardToFunc(dev.command_inout)
|
||||||
dev.write_attribute = self._applyGuardToFunc(dev.write_attribute,
|
dev.__dict__['write_attribute'] = self._applyGuardToFunc(dev.write_attribute,
|
||||||
'attr_write')
|
'attr_write')
|
||||||
dev.read_attribute = self._applyGuardToFunc(dev.read_attribute,
|
dev.__dict__['read_attribute'] = self._applyGuardToFunc(dev.read_attribute,
|
||||||
'attr_read')
|
'attr_read')
|
||||||
dev.attribute_query = self._applyGuardToFunc(dev.attribute_query,
|
dev.__dict__['attribute_query'] = self._applyGuardToFunc(dev.attribute_query,
|
||||||
'attr_query')
|
'attr_query')
|
||||||
return dev
|
return dev
|
||||||
|
|
||||||
|
@ -25,10 +25,10 @@ import math
|
|||||||
import re
|
import re
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from frappy.core import Drivable, HasIO, Writable, StatusType, \
|
from frappy.core import Command, Drivable, HasIO, Writable, StatusType, \
|
||||||
Parameter, Property, Readable, StringIO, Attached, IDLE, RAMPING, nopoll
|
Parameter, Property, Readable, StringIO, Attached, IDLE, RAMPING, nopoll
|
||||||
from frappy.datatypes import EnumType, FloatRange, StringType, StructOf, BoolType, TupleOf
|
from frappy.datatypes import EnumType, FloatRange, StringType, StructOf, BoolType, TupleOf
|
||||||
from frappy.errors import HardwareError, ProgrammingError, ConfigError, RangeError
|
from frappy.errors import HardwareError, ProgrammingError, ConfigError
|
||||||
from frappy_psi.convergence import HasConvergence
|
from frappy_psi.convergence import HasConvergence
|
||||||
from frappy.states import Retry, Finish
|
from frappy.states import Retry, Finish
|
||||||
from frappy.mixins import HasOutputModule, HasControlledBy
|
from frappy.mixins import HasOutputModule, HasControlledBy
|
||||||
@ -218,7 +218,6 @@ class HasInput(HasControlledBy, MercuryChannel):
|
|||||||
class Loop(HasOutputModule, MercuryChannel, Drivable):
|
class Loop(HasOutputModule, MercuryChannel, Drivable):
|
||||||
"""common base class for loops"""
|
"""common base class for loops"""
|
||||||
output_module = Attached(HasInput, mandatory=False)
|
output_module = Attached(HasInput, mandatory=False)
|
||||||
control_active = Parameter(readonly=False)
|
|
||||||
ctrlpars = Parameter(
|
ctrlpars = Parameter(
|
||||||
'pid (proportional band, integral time, differential time',
|
'pid (proportional band, integral time, differential time',
|
||||||
StructOf(p=FloatRange(0, unit='$'), i=FloatRange(0, unit='min'), d=FloatRange(0, unit='min')),
|
StructOf(p=FloatRange(0, unit='$'), i=FloatRange(0, unit='min'), d=FloatRange(0, unit='min')),
|
||||||
@ -226,14 +225,15 @@ class Loop(HasOutputModule, MercuryChannel, Drivable):
|
|||||||
)
|
)
|
||||||
enable_pid_table = Parameter('', BoolType(), readonly=False)
|
enable_pid_table = Parameter('', BoolType(), readonly=False)
|
||||||
|
|
||||||
def set_output(self, active, source='HW'):
|
def set_output(self, active, source=None):
|
||||||
if active:
|
if active:
|
||||||
self.activate_control()
|
self.activate_control()
|
||||||
else:
|
else:
|
||||||
self.deactivate_control(source)
|
self.deactivate_control(source)
|
||||||
|
|
||||||
def set_target(self, target):
|
def set_target(self, target):
|
||||||
self.set_output(True)
|
if not self.control_active:
|
||||||
|
self.activate_control()
|
||||||
self.target = target
|
self.target = target
|
||||||
|
|
||||||
def read_enable_pid_table(self):
|
def read_enable_pid_table(self):
|
||||||
@ -254,9 +254,16 @@ class Loop(HasOutputModule, MercuryChannel, Drivable):
|
|||||||
def read_status(self):
|
def read_status(self):
|
||||||
return IDLE, ''
|
return IDLE, ''
|
||||||
|
|
||||||
|
@Command()
|
||||||
|
def control_off(self):
|
||||||
|
"""switch control off"""
|
||||||
|
# remark: this is needed in frappy_psi.trition.TemperatureLoop, as the heater
|
||||||
|
# output is not available there. We define it here as a convenience for the user.
|
||||||
|
self.write_control_active(False)
|
||||||
|
|
||||||
|
|
||||||
class ConvLoop(HasConvergence, Loop):
|
class ConvLoop(HasConvergence, Loop):
|
||||||
def deactivate_control(self, source):
|
def deactivate_control(self, source=None):
|
||||||
if self.control_active:
|
if self.control_active:
|
||||||
super().deactivate_control(source)
|
super().deactivate_control(source)
|
||||||
self.convergence_state.start(self.inactive_state)
|
self.convergence_state.start(self.inactive_state)
|
||||||
@ -372,16 +379,14 @@ class TemperatureLoop(TemperatureSensor, ConvLoop):
|
|||||||
super().doPoll()
|
super().doPoll()
|
||||||
self.read_setpoint()
|
self.read_setpoint()
|
||||||
|
|
||||||
def read_control_active(self):
|
def set_control_active(self, active):
|
||||||
active = self.query(f'DEV::{self.ENABLE}', off_on)
|
super().set_control_active(active)
|
||||||
self.set_output(active)
|
self.change(f'DEV::{self.ENABLE}', active, off_on)
|
||||||
return active
|
|
||||||
|
|
||||||
def write_control_active(self, value):
|
def initialReads(self):
|
||||||
if value:
|
# initialize control active from HW
|
||||||
raise RangeError('write to target to switch control on')
|
active = self.query(f'DEV::{self.ENABLE}', off_on)
|
||||||
self.set_output(value, 'user')
|
super().set_output(active, 'HW')
|
||||||
return self.change(f'DEV::{self.ENABLE}', value, off_on)
|
|
||||||
|
|
||||||
@nopoll # polled by read_setpoint
|
@nopoll # polled by read_setpoint
|
||||||
def read_target(self):
|
def read_target(self):
|
||||||
@ -413,7 +418,7 @@ class TemperatureLoop(TemperatureSensor, ConvLoop):
|
|||||||
self.change(f'DEV::{self.ENABLE}', True, off_on)
|
self.change(f'DEV::{self.ENABLE}', True, off_on)
|
||||||
super().set_target(target)
|
super().set_target(target)
|
||||||
|
|
||||||
def deactivate_control(self, source):
|
def deactivate_control(self, source=None):
|
||||||
if self.__ramping:
|
if self.__ramping:
|
||||||
self.__ramping = False
|
self.__ramping = False
|
||||||
# stop ramping setpoint
|
# stop ramping setpoint
|
||||||
@ -508,14 +513,16 @@ class PressureLoop(PressureSensor, HasControlledBy, ConvLoop):
|
|||||||
output_module = Attached(ValvePos, mandatory=False)
|
output_module = Attached(ValvePos, mandatory=False)
|
||||||
tolerance = Parameter(default=0.1)
|
tolerance = Parameter(default=0.1)
|
||||||
|
|
||||||
def read_control_active(self):
|
def set_control_active(self, active):
|
||||||
active = self.query('DEV::PRES:LOOP:FAUT', off_on)
|
super().set_control_active(active)
|
||||||
self.set_output(active)
|
if not active:
|
||||||
return active
|
self.self_controlled() # switches off auto flow
|
||||||
|
return self.change('DEV::PRES:LOOP:FAUT', active, off_on)
|
||||||
|
|
||||||
def write_control_active(self, value):
|
def initialReads(self):
|
||||||
self.set_output(value, 'user')
|
# initialize control active from HW
|
||||||
return self.change('DEV::PRES:LOOP:FAUT', value, off_on)
|
active = self.query('DEV::PRES:LOOP:FAUT', off_on)
|
||||||
|
super().set_output(active, 'HW')
|
||||||
|
|
||||||
def read_target(self):
|
def read_target(self):
|
||||||
return self.query('DEV::PRES:LOOP:PRST')
|
return self.query('DEV::PRES:LOOP:PRST')
|
||||||
@ -560,14 +567,15 @@ class HasAutoFlow:
|
|||||||
if value:
|
if value:
|
||||||
self.needle_valve.controlled_by = self.name
|
self.needle_valve.controlled_by = self.name
|
||||||
else:
|
else:
|
||||||
|
if self.needle_valve.control_active:
|
||||||
|
self.needle_valve.set_target(self.flowpars[1][0]) # flow min
|
||||||
if self.needle_valve.controlled_by != SELF:
|
if self.needle_valve.controlled_by != SELF:
|
||||||
self.needle_valve.controlled_by = SELF
|
self.needle_valve.controlled_by = SELF
|
||||||
self.needle_valve.write_target(self.flowpars[1][0]) # flow min
|
|
||||||
return value
|
return value
|
||||||
|
|
||||||
def auto_flow_off(self):
|
def auto_flow_off(self, source=None):
|
||||||
if self.auto_flow:
|
if self.auto_flow:
|
||||||
self.log.warning('switch auto flow off')
|
self.log.warning(f'switched auto flow off by {source or self.name}')
|
||||||
self.write_auto_flow(False)
|
self.write_auto_flow(False)
|
||||||
|
|
||||||
|
|
||||||
|
@ -83,7 +83,7 @@ class Motor(HasOffset, HasStates, PersistentMixin, HasIO, Drivable):
|
|||||||
|
|
||||||
ioClass = PhytronIO
|
ioClass = PhytronIO
|
||||||
_step_size = None # degree / step
|
_step_size = None # degree / step
|
||||||
_blocking_error = None # None or a string indicating the reason of an error needing reset
|
_blocking_error = None # None or a string indicating the reason of an error needing clear_errors
|
||||||
_running = False # status indicates motor is running
|
_running = False # status indicates motor is running
|
||||||
|
|
||||||
STATUS_MAP = {
|
STATUS_MAP = {
|
||||||
@ -121,10 +121,10 @@ class Motor(HasOffset, HasStates, PersistentMixin, HasIO, Drivable):
|
|||||||
if not axisbit & active_axes: # power cycle detected and this axis not yet active
|
if not axisbit & active_axes: # power cycle detected and this axis not yet active
|
||||||
self.set('P37S', axisbit | active_axes) # activate axis
|
self.set('P37S', axisbit | active_axes) # activate axis
|
||||||
if now < self.alive_time + 7 * 24 * 3600: # the device was running within last week
|
if now < self.alive_time + 7 * 24 * 3600: # the device was running within last week
|
||||||
# inform the user about the loss of position by the need of doing reset_error
|
# inform the user about the loss of position by the need of doing clear_errors
|
||||||
self._blocking_error = 'lost position'
|
self._blocking_error = 'lost position'
|
||||||
else: # do reset silently
|
else: # do silently
|
||||||
self.reset_error()
|
self.clear_errors()
|
||||||
self.alive_time = now
|
self.alive_time = now
|
||||||
self.saveParameters()
|
self.saveParameters()
|
||||||
return now
|
return now
|
||||||
@ -171,7 +171,7 @@ class Motor(HasOffset, HasStates, PersistentMixin, HasIO, Drivable):
|
|||||||
def write_target(self, value):
|
def write_target(self, value):
|
||||||
self.read_alive_time()
|
self.read_alive_time()
|
||||||
if self._blocking_error:
|
if self._blocking_error:
|
||||||
self.status = ERROR, 'reset needed after ' + self._blocking_error
|
self.status = ERROR, 'clear_errors needed after ' + self._blocking_error
|
||||||
raise HardwareError(self.status[1])
|
raise HardwareError(self.status[1])
|
||||||
self.saveParameters()
|
self.saveParameters()
|
||||||
if self.backlash:
|
if self.backlash:
|
||||||
@ -261,7 +261,7 @@ class Motor(HasOffset, HasStates, PersistentMixin, HasIO, Drivable):
|
|||||||
self.start_machine(self.stopping, status=(BUSY, 'stopping'))
|
self.start_machine(self.stopping, status=(BUSY, 'stopping'))
|
||||||
|
|
||||||
@Command
|
@Command
|
||||||
def reset_error(self):
|
def clear_errors(self):
|
||||||
"""Reset error, set position to encoder"""
|
"""Reset error, set position to encoder"""
|
||||||
self.read_value()
|
self.read_value()
|
||||||
if self._blocking_error:
|
if self._blocking_error:
|
||||||
@ -286,3 +286,4 @@ class Motor(HasOffset, HasStates, PersistentMixin, HasIO, Drivable):
|
|||||||
self.read_value()
|
self.read_value()
|
||||||
self.status = 'IDLE', 'after error reset'
|
self.status = 'IDLE', 'after error reset'
|
||||||
self._blocking_error = None
|
self._blocking_error = None
|
||||||
|
self.target = self.value # clear error in target
|
||||||
|
@ -111,6 +111,7 @@ class SeaClient(ProxyClient, Module):
|
|||||||
_connect_thread = None
|
_connect_thread = None
|
||||||
_service_manager = None
|
_service_manager = None
|
||||||
_instance = None
|
_instance = None
|
||||||
|
_last_connect = 0
|
||||||
|
|
||||||
def __init__(self, name, log, opts, srv):
|
def __init__(self, name, log, opts, srv):
|
||||||
nodename = srv.node_cfg.get('name') or srv.node_cfg.get('equipment_id')
|
nodename = srv.node_cfg.get('name') or srv.node_cfg.get('equipment_id')
|
||||||
@ -135,6 +136,10 @@ class SeaClient(ProxyClient, Module):
|
|||||||
ProxyClient.__init__(self)
|
ProxyClient.__init__(self)
|
||||||
Module.__init__(self, name, log, opts, srv)
|
Module.__init__(self, name, log, opts, srv)
|
||||||
|
|
||||||
|
def doPoll(self):
|
||||||
|
if not self.asynio and time.time() > self._last_connect + 10:
|
||||||
|
self._connect_thread = mkthread(self._connect, None)
|
||||||
|
|
||||||
def register_obj(self, module, obj):
|
def register_obj(self, module, obj):
|
||||||
self.objects.add(obj)
|
self.objects.add(obj)
|
||||||
for k, v in module.path2param.items():
|
for k, v in module.path2param.items():
|
||||||
@ -146,6 +151,7 @@ class SeaClient(ProxyClient, Module):
|
|||||||
self._connect_thread = mkthread(self._connect, start_events.get_trigger())
|
self._connect_thread = mkthread(self._connect, start_events.get_trigger())
|
||||||
|
|
||||||
def _connect(self, started_callback):
|
def _connect(self, started_callback):
|
||||||
|
self._last_connect = time.time()
|
||||||
if self._instance:
|
if self._instance:
|
||||||
if not self._service_manager:
|
if not self._service_manager:
|
||||||
if self._service_manager is None:
|
if self._service_manager is None:
|
||||||
@ -192,36 +198,40 @@ class SeaClient(ProxyClient, Module):
|
|||||||
self.syncio.writeline(b'seauser seaser')
|
self.syncio.writeline(b'seauser seaser')
|
||||||
assert self.syncio.readline() == b'Login OK'
|
assert self.syncio.readline() == b'Login OK'
|
||||||
self.log.info('connected to %s', self.uri)
|
self.log.info('connected to %s', self.uri)
|
||||||
self.syncio.flush_recv()
|
try:
|
||||||
ft = 'fulltransAct' if quiet else 'fulltransact'
|
self.syncio.flush_recv()
|
||||||
self.syncio.writeline(('%s %s' % (ft, command)).encode())
|
ft = 'fulltransAct' if quiet else 'fulltransact'
|
||||||
result = None
|
self.syncio.writeline(('%s %s' % (ft, command)).encode())
|
||||||
deadline = time.time() + 10
|
result = None
|
||||||
while time.time() < deadline:
|
deadline = time.time() + 10
|
||||||
try:
|
while time.time() < deadline:
|
||||||
reply = self.syncio.readline()
|
reply = self.syncio.readline()
|
||||||
if reply is None:
|
if reply is None:
|
||||||
continue
|
continue
|
||||||
except ConnectionClosed:
|
reply = reply.decode()
|
||||||
break
|
if reply.startswith('TRANSACTIONSTART'):
|
||||||
reply = reply.decode()
|
result = []
|
||||||
if reply.startswith('TRANSACTIONSTART'):
|
continue
|
||||||
result = []
|
if reply == 'TRANSACTIONFINISHED':
|
||||||
continue
|
if result is None:
|
||||||
if reply == 'TRANSACTIONFINISHED':
|
self.log.info('missing TRANSACTIONSTART on: %s', command)
|
||||||
|
return ''
|
||||||
|
if not result:
|
||||||
|
return ''
|
||||||
|
return '\n'.join(result)
|
||||||
if result is None:
|
if result is None:
|
||||||
self.log.info('missing TRANSACTIONSTART on: %s', command)
|
self.log.info('swallow: %s', reply)
|
||||||
return ''
|
continue
|
||||||
if not result:
|
if not result:
|
||||||
return ''
|
result = [reply.split('=', 1)[-1]]
|
||||||
return '\n'.join(result)
|
else:
|
||||||
if result is None:
|
result.append(reply)
|
||||||
self.log.info('swallow: %s', reply)
|
except ConnectionClosed:
|
||||||
continue
|
try:
|
||||||
if not result:
|
self.syncio.disconnect()
|
||||||
result = [reply.split('=', 1)[-1]]
|
except Exception:
|
||||||
else:
|
pass
|
||||||
result.append(reply)
|
self.syncio = None
|
||||||
raise TimeoutError('no response within 10s')
|
raise TimeoutError('no response within 10s')
|
||||||
|
|
||||||
def _rxthread(self, started_callback):
|
def _rxthread(self, started_callback):
|
||||||
@ -231,6 +241,11 @@ class SeaClient(ProxyClient, Module):
|
|||||||
if reply is None:
|
if reply is None:
|
||||||
continue
|
continue
|
||||||
except ConnectionClosed:
|
except ConnectionClosed:
|
||||||
|
try:
|
||||||
|
self.asynio.disconnect()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
self.asynio = None
|
||||||
break
|
break
|
||||||
try:
|
try:
|
||||||
msg = json.loads(reply)
|
msg = json.loads(reply)
|
||||||
@ -463,7 +478,6 @@ class SeaModule(Module):
|
|||||||
descr['params'].pop(0)
|
descr['params'].pop(0)
|
||||||
else:
|
else:
|
||||||
# filter by relative paths
|
# filter by relative paths
|
||||||
# rel_paths = rel_paths.split()
|
|
||||||
result = []
|
result = []
|
||||||
is_running = None
|
is_running = None
|
||||||
for rpath in rel_paths:
|
for rpath in rel_paths:
|
||||||
|
@ -25,7 +25,7 @@ from frappy.core import Writable, Parameter, Readable, Drivable, IDLE, WARN, BUS
|
|||||||
Done, Property
|
Done, Property
|
||||||
from frappy.datatypes import EnumType, FloatRange, StringType
|
from frappy.datatypes import EnumType, FloatRange, StringType
|
||||||
from frappy.lib.enum import Enum
|
from frappy.lib.enum import Enum
|
||||||
from frappy_psi.mercury import MercuryChannel, Mapped, off_on, HasInput, SELF
|
from frappy_psi.mercury import MercuryChannel, Mapped, off_on, HasInput
|
||||||
from frappy_psi import mercury
|
from frappy_psi import mercury
|
||||||
|
|
||||||
actions = Enum(none=0, condense=1, circulate=2, collect=3)
|
actions = Enum(none=0, condense=1, circulate=2, collect=3)
|
||||||
@ -256,15 +256,14 @@ class TemperatureLoop(ScannerChannel, mercury.TemperatureLoop):
|
|||||||
ctrlpars = Parameter('pid (gain, integral (inv. time), differential time')
|
ctrlpars = Parameter('pid (gain, integral (inv. time), differential time')
|
||||||
system_channel = Property('system channel name', StringType(), 'MC')
|
system_channel = Property('system channel name', StringType(), 'MC')
|
||||||
|
|
||||||
def write_control_active(self, value):
|
def set_control_active(self, active):
|
||||||
if self.system_channel:
|
if self.system_channel:
|
||||||
self.change('SYS:DR:CHAN:%s' % self.system_channel, self.slot.split(',')[0], str)
|
self.change('SYS:DR:CHAN:%s' % self.system_channel, self.slot.split(',')[0], str)
|
||||||
if value:
|
if active:
|
||||||
self.change('DEV::TEMP:LOOP:FILT:ENAB', 'ON', str)
|
self.change('DEV::TEMP:LOOP:FILT:ENAB', 'ON', str)
|
||||||
if self.output_module:
|
if self.output_module:
|
||||||
limit = self.output_module.read_limit() or None # None: max. limit
|
limit = self.output_module.read_limit()
|
||||||
self.output_module.write_limit(limit)
|
self.output_module.write_limit(limit)
|
||||||
return super().write_control_active(value)
|
|
||||||
|
|
||||||
|
|
||||||
class HeaterOutput(HasInput, MercuryChannel, Writable):
|
class HeaterOutput(HasInput, MercuryChannel, Writable):
|
||||||
@ -286,7 +285,7 @@ class HeaterOutput(HasInput, MercuryChannel, Writable):
|
|||||||
return self.value
|
return self.value
|
||||||
|
|
||||||
def write_target(self, value):
|
def write_target(self, value):
|
||||||
self.write_controlled_by(SELF)
|
self.self_controlled()
|
||||||
if self.resistivity:
|
if self.resistivity:
|
||||||
# round to the next voltage step
|
# round to the next voltage step
|
||||||
value = round(sqrt(value * self.resistivity)) ** 2 / self.resistivity
|
value = round(sqrt(value * self.resistivity)) ** 2 / self.resistivity
|
||||||
@ -301,13 +300,12 @@ class HeaterOutputWithRange(HeaterOutput):
|
|||||||
|
|
||||||
def read_limit(self):
|
def read_limit(self):
|
||||||
maxcur = self.query('DEV::TEMP:LOOP:RANGE') # mA
|
maxcur = self.query('DEV::TEMP:LOOP:RANGE') # mA
|
||||||
|
if maxcur == 0:
|
||||||
|
maxcur = 100 # mA
|
||||||
return self.read_resistivity() * maxcur ** 2 # uW
|
return self.read_resistivity() * maxcur ** 2 # uW
|
||||||
|
|
||||||
def write_limit(self, value):
|
def write_limit(self, value):
|
||||||
if value is None:
|
maxcur = sqrt(value / self.read_resistivity())
|
||||||
maxcur = 100 # max. allowed current 100mA
|
|
||||||
else:
|
|
||||||
maxcur = sqrt(value / self.read_resistivity())
|
|
||||||
for cur in 0.0316, 0.1, 0.316, 1, 3.16, 10, 31.6, 100:
|
for cur in 0.0316, 0.1, 0.316, 1, 3.16, 10, 31.6, 100:
|
||||||
if cur > maxcur * 0.999:
|
if cur > maxcur * 0.999:
|
||||||
maxcur = cur
|
maxcur = cur
|
||||||
|
@ -249,15 +249,22 @@ class Mod(HasStates, Drivable):
|
|||||||
self._my_time += 1
|
self._my_time += 1
|
||||||
|
|
||||||
|
|
||||||
|
class Started(RuntimeError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
def create_module():
|
def create_module():
|
||||||
updates = []
|
updates = []
|
||||||
obj = Mod('obj', LoggerStub(), {'description': ''}, ServerStub(updates))
|
obj = Mod('obj', LoggerStub(), {'description': ''}, ServerStub(updates))
|
||||||
obj.initModule()
|
obj.initModule()
|
||||||
obj.statelist = []
|
obj.statelist = []
|
||||||
try:
|
try:
|
||||||
obj._Module__pollThread(obj.polledModules, None)
|
def started():
|
||||||
except TypeError:
|
raise Started()
|
||||||
pass # None is not callable
|
# run __pollThread until Started is raised (after initial phase)
|
||||||
|
obj._Module__pollThread(obj.polledModules, started)
|
||||||
|
except Started:
|
||||||
|
pass
|
||||||
updates.clear()
|
updates.clear()
|
||||||
return obj, updates
|
return obj, updates
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user