add a hook for reads to be done initially
inital reads from HW should be done in the thread started by startModule, not in startModule itself. - add a hook method 'initialReads' for this + add doc for init methods + fix some errors in doc Change-Id: I914e3b7ee05050eea1ee8aff3461030adf08a461 Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/31374 Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de> Reviewed-by: Enrico Faulhaber <enrico.faulhaber@frm2.tum.de> Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
This commit is contained in:
parent
997e8e26e9
commit
138b84e84c
@ -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
|
||||||
|
@ -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.
|
||||||
|
:return: the formatted error message
|
||||||
|
|
||||||
Use stripped=True (or str()) for the following cases, as the last method can be derived from the context:
|
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>
|
- stored in pobj.readerror: read_<pobj.name>
|
||||||
- error message from a change command: write_<pname>
|
- error message from a change command: write_<pname>
|
||||||
- error message from a read command: read_<pname>
|
- error message from a read command: read_<pname>
|
||||||
|
|
||||||
Use stripped=False for the log file, as the related parameter is not known
|
Use stripped=False for the log file, as the related parameter is not known
|
||||||
:return: the formatted error message
|
|
||||||
"""
|
"""
|
||||||
mlist = self.raising_methods
|
mlist = self.raising_methods
|
||||||
if mlist and stripped:
|
if mlist and stripped:
|
||||||
|
@ -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:
|
||||||
|
# TODO when needed: here we might add a call to a method :meth:`beforeWriteInit`
|
||||||
|
mobj.writeInitParams()
|
||||||
|
mobj.initialReads()
|
||||||
|
# call all read functions a first time
|
||||||
|
for m in polled_modules:
|
||||||
for mobj, rfunc, _ in m.pollInfo.polled_parameters:
|
for mobj, rfunc, _ in m.pollInfo.polled_parameters:
|
||||||
mobj.callPollFunc(rfunc, raise_com_failed=True)
|
mobj.callPollFunc(rfunc, raise_com_failed=True)
|
||||||
|
# TODO when needed: here we might add calls to a method :meth:`afterInitPolls`
|
||||||
|
break
|
||||||
except CommunicationFailedError as e:
|
except CommunicationFailedError as e:
|
||||||
# when communication failed, probably all parameters and may be more modules are affected.
|
# 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
|
# 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
|
# trying and let the server accept connections, further polls might success later
|
||||||
|
if started_callback:
|
||||||
self.log.error('communication failure on startup: %s', e)
|
self.log.error('communication failure on startup: %s', e)
|
||||||
started_callback()
|
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()
|
||||||
|
@ -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