ChimeraTK-ApplicationCore
04.01.00
|
NOTICE FOR FUTURE RELEASES: AVOID CHANGING THE NUMBERING! The tests refer to the sections, incl. links and unlinked references from tests or other parts of the specification. These break, or even worse become wrong, when they are not changed consistenty!
This document describes how initial values are propagated from the control system persistency layer, from the devices and from application modules into the attached components (control system, devices and other application modules).
This specification goes beyond ApplicationCore. It has impact on other ChimeraTK libraries like DeviceAccess, the ControlSystemAdapter and even backends and adapter implementations.
ProcessArray
or any other NDRegisterAccessor
implementation. The point in time when the value becomes available is well-defined, as described in the section C (high-level requirements).ApplicationModule
s at the start of the ApplicationModule::mainLoop()
. No call to TransferElement::read()
etc. is required. This implies that the ApplicationModule::mainLoop()
is not started until all initial values are available, including those coming from devices which might potentially be offline, or from other ApplicationModule
s.ApplicationModule
implementations can either provide initial values for their outputs in ApplicationModule::prepare()
(if the initial value doesn't depend on any inputs) or right after the start of the ApplicationModule::mainLoop()
(if the initial value needs to be computed from the incoming initial values of the inputs).DataValidity::faulty
flag set. It is not visible to user code in the ApplicationModule::mainLoop()
.DataValidity::faulty
flag is set, but the VersionNumber is not {nullptr}
. (*)ApplicationModule
s and devices might wait for initial values from other ApplicationModule
s and devices, the modules might end up in a dead lock due to a circular connection. The circular connection is legal, but the dead lock situation needs to be broken by one ApplicationModule
writing its initial value during ApplicationModule::prepare()
.DataValidity::faulty
flag until they have received the first valid value.DataValidity::faulty
is set), despite the value might present an inconsistent state with other process variables. If it gets propagated to the control system, other applications might act on an again inconsistent state.NDRegisterAccessor
implementations (including but not limited to the ProcessArray
) have the DataValidity::faulty
flag set after construction for the receiving end. This ensures, all data is marked as faulty
as long as no sensible initial values have been propagated. The sending end has DataValidity::ok
after construction, so that the first written data automatically propagates the ok state by default. For bidirectional variables, this is the case for both directions separately. [ T]NDRegisterAccessor
implementations have initially a VersionNumber
constructed with a nullptr
, which allows to check whether this variable is still at its "value after construction", or the initial value propagation already took place. [ T]ApplicationModule
(and ThreadedFanOut
/TriggerFanOut
) propagate the DataValidity
of its outputs according to the state of all inputs. This behaviour is identical to later during normal data processing and specified in the Technical specification: data validity propagation. [ T]ApplicationBase::run()
is called, or soon after (major parts of the application will be blocked until it's done). If the persistency layer can persist the DataValidity
, the initial value have the correct validity. Otherwise, initial values always have the DataValidity::ok
.ApplicationBase::run()
has been called. They might or might not be initial values of other modues. The control system adapter does not expect any specific behaviour. Entities writing to these variables do not need to take any special precautions.ApplicationModule
s:ApplicationModule::prepare()
, if the value does not depend on any input values (since input values are not available during prepare()
). [ T]ApplicationModule::mainLoop()
before calling any read
function, if they depend on initial values of the inputs. Typically, to propagate the initial values of its inputs, an ApplicationModule
will run its computations and write its outputs first before waiting for new data with a blocking read()
and the end of the processing loop. The application author needs to take into account that the ApplicationModule::mainLoop()
will only start after all inputs have their initial values available, which might depend on the avaialbility of devices. [ T]ApplicationModule::prepare()
all devices are still closed, any writes to device variables at this point need to be delayed until the device is open. As the ExceptionHandlingDecorator takes care of this, the appciation module can just call write() on its output and does not have to do any special actions here (see Technical specification: Exception handling for device runtime errors B.2.3).ApplicationModule
s:ApplicationModule::mainLoop()
(but already in the same thread which later executes the mainLoop()
).TransferElement::read()
, which freezes until the initial value has arrived, both with AccessMode::wait_for_new_data
and without.AccessMode::wait_for_new_data
, so the initial value can be received. (see. 10) [ T]ThreadedFanOut
and TriggerFanOut
(does not apply to the accessor-like fan outs FeedingFanOut
and ConsumingFanOut
)Application::makeConstant()
): [ T]AccessMode::wait_for_new_data
the value can be read exactly once. This is enough to propagate the initial value. Any further read() to the variable will block infinitely.ApplicationModule
threads are starting (just like initial values written in ApplicationModule::prepare()
)ScalarPushInputWB
, ScalarOutputPushRB
and the array variants) behave like their unidirectional pendants, i.e. the existence of the return channel is ignored during the initial value propagation.ApplicationModule
with an initial value written in ApplicationModule::prepare()
and later never written again behaves in the same way as a constant.NDRegisterAccessor
must implement 1. separately.NDRegisterAccessor
must implement 2. separately. All accessors should already have a VersionNumber
data member called currentVersion
or similar, it simply needs to be constructed with a nullptr
as an argument.ApplicationModule::mainLoopWrapper()
before the call to mainLoop()
.ThreadedFanOut::run()
) like this:ThreadedFanOut
, the TriggerFanOut
has only poll-type data inputs which are all coming from the same device. Data inputs cannot come from non-devices.TriggerFanOut::run()
) like this:TransferGroup
All points are already covered by Technical specification: Exception handling for device runtime errors V1.0.
All points are already covered by Technical specification: Exception handling for device runtime errors V1.0.
(This section refers to the class Application
, not to the user-defined application.)
As ProcessArrays do not have a synchronous read channel which can be used to obtain the "current value", the implementation freezes all read operations (even readNonBlocking() and readLatest()) until a first value has been send. This is consistent with the behaviour of the ExceptionHandlingDecorator, which freezes until the device has become available and the synchronous channel can deliver data.
Comment: The original idea of an extra function readBlocking() only for process arrays does not work, because it breaks abstraction. To use it one would have to dynamic cast a TransferElement to ProcessArray, which does not work when the object is decorated (which it always is).
DataValidity
flags for each direction.UnidirectionalProcessArray
is correct at the moment).UnidirectionalProcessArray
is correct at the moment).TransferElement::readTransfer()
etc, i.e. all functions like do[...]Transfer[...]()
should have non-virtual pendants without do
.do[...]Transfer[...]()
function, but place a try-catch-block around to catch all ChimeraTK exceptionsdoReadTransferNonBlocking()
and doReadTransferLatest()
must return false (there was no new data), except for the ExceptionHandlingDecorator which has to return true if it will do a recovery in postRead() and there will be new data.doWriteTransfer()
shall return true (dataLost), except for the ExceptionHandlingDecorator. It should return true only if the data of the recovery accessor is replaced and the previous value has not been written to the hardware.postRead()
and postWrite()
must always be called. It currently depends on the boolean return value if there is one. Instead this value has to be handed to postRead()
and postWrite()
as an argument. Only the implementation can decide what it has to do and what can be skipped.TransferElement::postRead()
resp. TransferElement::postWrite()
non-virtual wrappers for the post-actions already exist. In these functions, the stored exception should be thrown.do[...]
functions. This applies to both the transfer functions and the pre/post actions (for the latter it should be already the case).faultCounter
variable itself is currently part of the EntitiyOwner. It will be moved to a helper class, so multiple instances can be used (needed for the TriggerFanOut). It is the responsibility of the decorators which manipulate the DataFaultCounter to increase the counter when they come up with faulty data in the inital state (see Technical specification: data validity propagation Specification version V1.0).Device::isOpened()
check), but in a wrong way. After the NDRegisterAccessor
has been fixed, this needs to be removed.Probably all points are duplicates with Technical specification: Exception handling for device runtime errors V1.0.
DeviceModule::writeAfterOpen/writeRecoveryOpen
lists.Some points are duplicates with Technical specification: Exception handling for device runtime errors V1.0.
readLatest()
operation to always block in the very first call until the device is available.readLatest()
is always the first read
-type function called, it is acceptable if all read
-type functions implement this behaviour. Choose whatever is easier to implement, update the implementation section of this specification to match the chosen implementation.VariableNetworkNode::hasInitialValue()
into VariableNetworkNode::initialValueUpdateMode()
, and change the return type to UpdateMode
. Remove the InitialValueMode enum.VariableNetworkNode::initialValueType()
accordingly.UnidirectionalProcessArray
uses always a default start value of DataValidity::ok
, but overwrites this with DataValidity::faulty
for the receivers in the factory function UnidirectionalProcessArray::createSynchronizedProcessArray()
(both implementations, see UnidirectionalProcessArray.h). This can be solved more elegant after the DeviceAccess interface change described above.