diff --git a/source/schema_injection.rst b/source/schema_injection.rst new file mode 100644 index 0000000000000000000000000000000000000000..d88a8ec60ad51f73c9b6fe63c241ea05023fee7f --- /dev/null +++ b/source/schema_injection.rst @@ -0,0 +1,148 @@ +Schema Injection +================ +A schema injection is a modification of a device schema, to bring further +properties visible outside or to update attributes of existing properties +(such as the maximum size of a vector). Any number and types of properties can +be injected, whether parameters or slots. + +In this example, we will define a slot that adds a boolean `injectedProperty`. + +Begin by defining a slot that will call the `extend` function: + +.. code-block:: Python + + from karabo.bound import BOOL_ELEMENT, PythonDevice, SLOT_ELEMENT + + class MyDevice(PythonDevice): + + @staticmethod + def expectedParameters(expected): + ( + SLOT_ELEMENT(expected).key('extend') + .displayedName('Extend') + .description('Extend the schema and introduce a new boolean property') + .commit(), + ) + + # Register the slot in the device schema + def onInitialization(self): + self.KARABO_SLOT(self.extend) + + +There are two methods to injecting properties: `appendSchema` and `updateSchema`, +both inherited from :class:`karabo.bound.PythonDevice`. + +:func:`appendSchema` will add properties to the device, and can be called any +number of time. +:func:`updateSchema` will take the the original schema of the device, and add +the new one to the device. However, it will discard what has been previously +appended or updated! + +Nonetheless, if :func:`appendSchema` is called after :func:`updateSchema`, then +the parameters from both injections are kept. + +If :func:`updateSchema` is called with an empty schema, then the device will be +reset to its original schema, as defined in :func:`MyDevice.expectedParameters`. + +This works, as internally, a device keeps three schemas: its `static` schema, +as defined in `expectedParameters`, the `injected` schema, which is modified on +injections, and the `full` schema, the combination of both which is exposed to +the rest of the ecosystem. + +.. code-block:: Python + + from karabo.bound import BOOL_ELEMENT, DAQPolicy, Schema + + def extend(self): + # Define a new Schema object + schema = Schema() + + # Then populate that new Schema + ( + BOOL_ELEMENT(schema).key('injectedProperty') + .displayedName('Hello') + .description('A fresh property') + .daqPolicy(DAQPolicy.OMIT) + .readOnly().initialValue(True) + .commit() + + # Finally, append the schema to our existing device + self.appendSchema(schema) # Or self.updateSchema(schema) + + +Once a device has been modified, either of these log messages will be given:: + + INFO MyDevice : Schema updated + + INFO MyDevice : Schema appended + + +Re-injecting a property, such as `injectedProperty`, will keep its current value +if possible. However, with different types, an error will be raised if the +value cannot be cast e.g. going from `UINT32_ELEMENT` to `INT16_ELEMENT` with a +value out of bound. + +Check Whether A Property Exists +------------------------------- +If your device has an update loop, you can either use flags to check +whether schema injection has already been done, or use :func:`getFullSchema`: + +.. code-block:: Python + + def update(self): + if self.getFullSchema().has("injectedProperty"): + self.set("injectedProperty", not self.get("injectedProperty")) + + +Injected Properties and DAQ +--------------------------- +Injected Properties and the DAQ need some ground rules in order to record these +properties correctly. + +In order for the DAQ to record injected properties, the DAQ needs to request the +updated schema again, using the Run Controller's :func:`applyConfiguration` slot. + +This can be prone to operator errors, and therefore it is recommended that only +properties injected at instantiation to be recorded. + +However, a common need for Schema updates is to specify the `maxSize` attribute +fo vector or table elements, as the DAQ only supports fixed length arrays, of +which the size has to be predefined. + +For that, there is the special function :func:`karabo.bound.PythonDevice.appendSchemaMaxSize` +which, given a property path and the new length, will update the schema +accordingly. + +Such a vector: + +.. code-block:: Python + + from karabo.bound import NODE_ELEMENT, VECTOR_INT32_ELEMENT + + @staticmethod + def expectedParameters(expected): + ( + NODE_ELEMENT(expected).key('node').commit(), + + VECTOR_INT32_ELEMENT(expected').key('node.vector') + .displayedName('Vector') + .readOnly() + .maxSize(5) + .commit(), + ) + + +Can have its `maxSize` be redefined as follows: + +.. code-block:: Python + + self.appendSchemaMaxSize('node.vector', 50) + +If several updates are made, then it is recommended to set `emitFlag` to false for +all vectors apart of the last one. Only a single update will be then sent: + +.. code-block:: Python + + self.appendSchemaMaxSize('node.vector0', 50, emitFlag=False) + self.appendSchemaMaxSize('node.vector1', 50, emitFlag=False) + self.appendSchemaMaxSize('node.vector2', 50)