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
|
||||
.......
|
||||
|
||||
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:
|
||||
|
||||
|
@ -95,5 +95,23 @@ Example code:
|
||||
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
|
||||
|
@ -12,7 +12,7 @@ Module Base Classes
|
||||
...................
|
||||
|
||||
.. autoclass:: frappy.modules.Module
|
||||
:members: earlyInit, initModule, startModule
|
||||
:members: earlyInit, initModule, startModule, initialReads
|
||||
|
||||
.. autoclass:: frappy.modules.Readable
|
||||
:members: Status
|
||||
|
@ -1163,9 +1163,9 @@ class ValueType(DataType):
|
||||
|
||||
The optional (callable) validator can be used to restrict values to a
|
||||
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
|
||||
`dict(value)` is called for validation.
|
||||
``dict(value)`` is called for validation.
|
||||
|
||||
Notes:
|
||||
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
|
||||
|
||||
: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
|
||||
|
||||
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
|
||||
if mlist and stripped:
|
||||
|
@ -629,6 +629,16 @@ class Module(HasAccessibles):
|
||||
mkthread(self.__pollThread, self.polledModules, start_events.get_trigger())
|
||||
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):
|
||||
"""polls important parameters like value and status
|
||||
|
||||
@ -678,15 +688,10 @@ class Module(HasAccessibles):
|
||||
|
||||
before polling, parameters which need hardware initialisation are written
|
||||
"""
|
||||
for mobj in modules:
|
||||
mobj.writeInitParams()
|
||||
modules = [m for m in modules if m.enablePoll]
|
||||
if not modules: # no polls needed - exit thread
|
||||
started_callback()
|
||||
return
|
||||
polled_modules = [m for m in modules if m.enablePoll]
|
||||
if hasattr(self, 'registerReconnectCallback'):
|
||||
# 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:
|
||||
m.pollInfo.last_main = 0
|
||||
m.pollInfo.last_slow = 0
|
||||
@ -694,7 +699,7 @@ class Module(HasAccessibles):
|
||||
self.registerReconnectCallback('trigger_polls', trigger_all)
|
||||
|
||||
# collect all read functions
|
||||
for mobj in modules:
|
||||
for mobj in polled_modules:
|
||||
pinfo = mobj.pollInfo = PollInfo(mobj.pollinterval, self.triggerPoll)
|
||||
# trigger a poll interval change when self.pollinterval changes.
|
||||
if 'pollinterval' in mobj.valueCallbacks:
|
||||
@ -704,17 +709,32 @@ class Module(HasAccessibles):
|
||||
rfunc = getattr(mobj, 'read_' + pname)
|
||||
if rfunc.poll:
|
||||
pinfo.polled_parameters.append((mobj, rfunc, pobj))
|
||||
# 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()
|
||||
while True:
|
||||
try:
|
||||
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:
|
||||
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:
|
||||
# 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 = ()
|
||||
while True:
|
||||
now = time.time()
|
||||
|
@ -249,15 +249,22 @@ class Mod(HasStates, Drivable):
|
||||
self._my_time += 1
|
||||
|
||||
|
||||
class Started(RuntimeError):
|
||||
pass
|
||||
|
||||
|
||||
def create_module():
|
||||
updates = []
|
||||
obj = Mod('obj', LoggerStub(), {'description': ''}, ServerStub(updates))
|
||||
obj.initModule()
|
||||
obj.statelist = []
|
||||
try:
|
||||
obj._Module__pollThread(obj.polledModules, None)
|
||||
except TypeError:
|
||||
pass # None is not callable
|
||||
def started():
|
||||
raise Started()
|
||||
# run __pollThread until Started is raised (after initial phase)
|
||||
obj._Module__pollThread(obj.polledModules, started)
|
||||
except Started:
|
||||
pass
|
||||
updates.clear()
|
||||
return obj, updates
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user