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:

// Register the slot in the device schema
void MyDevice::expectedParameters(Schema& expected) {

    SLOT_ELEMENT(expected).key("extend")
            .displayedName("Extend")
            .description("Extend the schema and introduce a new boolean property")
            .commit();

}

// Register the slot in the constructor
MyDevice::MyDevice(const karabo::util::Hash& config) {
    KARABO_SLOT(extend);
}

There are two methods to injecting properties: appendSchema and updateSchema, both inherited from karabo::core::Device.

appendSchema() will add properties to the device, and can be called any number of time. 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 appendSchema() is called after updateSchema(), then the parameters from both injections are kept.

If updateSchema() is called with an empty schema, then the device will be reset to its original schema, as defined in 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.

void MyDevice::extend(void) {
    // 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
    this->appendSchema(schema);
    // Or
    // this->updateSchema(schema);
}

Once a device has been modified, either of these log message will be given:

INFO  MyDevice  : Schema appended

INFO  MyDevice  : Schema updated

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 getFullSchema():

void MyDevice::update(void) {

    const Schema schema = this->getFullSchema();
    if (schema.has("injectedProperty")) {
        this->set("injectedProperty"), !this->get<bool>("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 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 karabo::core::Device::appendSchemaMaxSize() which, given a property path and the new length, will update the schema accordingly.

Such a vector:

void MyDevice::expectedParameters(Schema& expected) {
    NODE_ELEMENT(expected).key("node").commit();
    VECTOR_UINT32_ELEMENT(expected).key("node.vector")
        .displayedName("Vector")
        .readOnly()
        .maxSize(5)
        .commit();
}

Can be resized as follows:

this->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:

this->appendSchemaMaxSize("node.vector0", 50, false);
this->appendSchemaMaxSize("node.vector1", 50, false);
this->appendSchemaMaxSize("node.vector2", 50);