Device Scenes

Karabo provides a protocol for devices to share predefined scenes. These allows the author of a device to provide what they think are a good starting point. Moreover, these are easily accessible from the topology panel in the GUI:

_images/default_scenes.png

A default scene can also be accessed by double-clicking on a device.

This section shows how to enable your device to have builtin scenes.

Implementing this functionality requires the creation of a scene in the scene editor, its conversion to a C++ ostringstream, and adding the requestScene slot to the device.

From Scene To Header File

Begin by drawing an adequate scene in the GUI’s scene editor, and save it locally on your computer as SVG (right-click on scene -> Save to File).

SVG is XML-based, and thus can be easily edited. Convert the SVG to a header file scenes.hh with a function. Thus the original SVG:

<?xml version="1.0"?>
<svg:svg xmlns:krb="http://karabo.eu/scene" xmlns:svg="http://www.w3.org/2000/svg" height="768" width="1024" krb:version="2">
        <svg:g krb:class="BoxLayout" krb:direction="0" krb:height="33" krb:width="87" krb:x="30" krb:y="26">
        <svg:rect height="24" width="39" x="0" y="0" krb:background="transparent" krb:class="Label" krb:font="Sans,9,-1,5,50,0,0,0,0,0" krb:foreground="#000000" krb:frameWidth="0" krb:text="Start"/>
        <svg:rect height="23" width="46" x="0" y="0" krb:class="DisplayComponent" krb:keys="Generator.start" krb:requires_confirmation="false" krb:widget="DisplayCommand"/>
</svg:g>
<svg:g krb:class="BoxLayout" krb:direction="0" krb:height="33" krb:width="81" krb:x="35" krb:y="61">
        <svg:rect height="24" width="37" x="0" y="0" krb:background="transparent" krb:class="Label" krb:font="Sans,9,-1,5,50,0,0,0,0,0" krb:foreground="#000000" krb:frameWidth="0" krb:text="Stop"/>
        <svg:rect height="23" width="43" x="0" y="0" krb:class="DisplayComponent" krb:keys="Generator.stop" krb:requires_confirmation="false" krb:widget="DisplayCommand"/>
</svg:g>
<svg:g krb:class="BoxLayout" krb:direction="0" krb:height="33" krb:width="145" krb:x="150" krb:y="33">
        <svg:rect height="24" width="95" x="0" y="0" krb:background="transparent" krb:class="Label" krb:font="Sans,9,-1,5,50,0,0,0,0,0" krb:foreground="#000000" krb:frameWidth="0" krb:text="Float property"/>
        <svg:rect height="23" width="49" x="0" y="0" krb:class="DisplayComponent" krb:keys="Generator.floatProperty" krb:widget="DisplayLabel"/>
</svg:g>
<svg:g krb:class="BoxLayout" krb:direction="0" krb:height="329" krb:width="514" krb:x="36" krb:y="119">
        <svg:rect height="24" width="47" x="0" y="0" krb:background="transparent" krb:class="Label" krb:font="Sans,9,-1,5,50,0,0,0,0,0" krb:foreground="#000000" krb:frameWidth="0" krb:text="image"/>
        <svg:rect height="319" width="465" x="0" y="0" krb:class="DisplayComponent" krb:keys="Generator.output.schema.image" krb:show_axes="true" krb:show_color_bar="true" krb:show_tool_bar="true" krb:widget="WebcamImage"/>
</svg:g>
</svg:svg>

Becomes:

#include <sstream>

std::string getControls(const std::string& instanceId) {
    std::ostringstream ret;
    // SVG header
    ret << "<?xml version=\"1.0\"?>"
        << "<svg:svg xmlns:krb=\"http://karabo.eu/scene\" xmlns:svg=\"http://www.w3.org/2000/svg\" "
        << "height=\"119\" width=\"150\" krb:version=\"2\">"
    // Start button label
        << "<svg:g krb:class=\"BoxLayout\" krb:direction=\"0\" krb:height=\"33\" krb:width=\"87\" krb:x=\"30\" krb:y=\"26\">"
        << "<svg:rect height=\"24\" width=\"39\" x=\"0\" y=\"0\" krb:background=\"transparent\" krb:class=\"Label\" krb:font=\"Sans,9,-1,5,50,0,0,0,0,0\" "
        << "krb:foreground=\"#000000\" krb:frameWidth=\"0\" krb:text=\"Start\"/>"
    // Start button itself
        << "<svg:rect height=\"23\" width=\"46\" x=\"0\" y=\"0\" krb:class=\"DisplayComponent\" "
        << "krb:keys=\"" << instanceId << ".start\" krb:requires_confirmation=\"false\" krb:widget=\"DisplayCommand\"/></svg:g>"
    // Stop button label
        << "<svg:g krb:class=\"BoxLayout\" krb:direction=\"0\" krb:height=\"33\" krb:width=\"81\" krb:x=\"35\" krb:y=\"61\">"
        << "<svg:rect height=\"24\" width=\"37\" x=\"0\" y=\"0\" krb:background=\"transparent\" krb:class=\"Label\" krb:font=\"Sans,9,-1,5,50,0,0,0,0,0\" "
        << "krb:foreground=\"#000000\" krb:frameWidth=\"0\" krb:text=\"Stop\"/>"
    // Stop button itself
        << "<svg:rect height=\"23\" width=\"43\" x=\"0\" y=\"0\" krb:class=\"DisplayComponent\" "
        << "krb:keys=\"" << instanceId << ".stop\" krb:requires_confirmation=\"false\" krb:widget=\"DisplayCommand\"/></svg:g>"
    // SVG footer
        << "</svg:svg>";
    return ret.str();
}

Add this file to your project.

Providing The Scene From Your Device

Add a read-only VECTOR_STRING_ELEMENT property called availableScenes to your expected parameters, and register and implement the requestScene slot. This is a predefined slot, which allows various actors to understand the scene protocol.

The slot takes a Hash params and lets the device reply with a Hash containing the origin, its datatype (deviceScene), and the scene serialized as xml:

#include "scenes.hh"

using namespace karabo::util;

// Define the list of scenes
void MyDevice::expectedParameters(karabo::util::Schema& expected) {

   VECTOR_STRING_ELEMENT(expected).key("availableScenes")
       .setSepcialDisplayType(KARABO_SCHEMA_DISPLAY_TYPE_SCENES)
       .readOnly().initialValue(std::vector<std::string>{"controls"})
       .commit();
}

// Register in the constructor that we have this functionality
MyDevice::MyDevice(const karabo::util::Hash& config)
    : karabo::core::Device<>(config)
{
    KARABO_SLOT(requestScene, karabo::util::Hash);
}

// The function that provides the scene
void MyDevice::requestScene(const karabo::util::Hash& params) {
    Hash result("type", "deviceScene", "origin", this->getInstanceId());
    Hash& payload = result.bindReference<Hash>("payload");

    payload.set("success", true);
    payload.set("name", "controls");
    payload.set("data", getControls());

    this->reply(result);
}

Providing Several Scenes

Would you want to provide several scenes (e.g., simple overview and control scene), you can define several functions in scenes.hh, and modify requestScene to check name in params:

#include "scenes.hh"

using namespace karabo::util;

// Define the list of scenes
void MyDevice::expectedParameters(karabo::util::Schema& expected) {

   VECTOR_STRING_ELEMENT(expected).key("availableScenes")
       .setSepcialDisplayType(KARABO_SCHEMA_DISPLAY_TYPE_SCENES)
       .readOnly().initialValue(std::vector<std::string>{"overview", "controls"})
       .commit();
}

// Register in the constructor that we have this functionality
MyDevice::MyDevice(const karabo::util::Hash& config)
    : karabo::core::Device<>(config)
{
    KARABO_SLOT(requestScene, karabo::util::Hash);
}

// The function that provides the scene
void MyDevice::requestScene(const karabo::util::Hash& params) {
    Hash result("type", "deviceScene", "origin", this->getInstanceId());
    Hash& payload = result.bindReference<Hash>("payload");
    payload.set("success", false);

    const std::string& which = param.get<std::string>("name");
    if ("overview" == which) {
        payload.set("name", "overview");
        payload.set("data", getOverview(this->getInstanceId()));
        payload.set("success", true);
    } else if ("controls" == which) {
        payload.set("name", "controls");
        payload.set("data", getControls(this->getInstanceId()));
        payload.set("success", true);
    } else {
        KARABO_LOG_ERROR << "Scene '" << which << "' was requested, but we don't have any";
    }

    this->reply(result);
}

Note

There is the convention that the default scene (of your choice) should be first in the availableScenes list: this will be the one served when double-clicking, for instance.

Linking To Other Devices Scenes

The following applies whether you want to link to another of your scenes or to another device’s scene.

Let’s say that you want to add links in your overview scene to your controls scene.

Create the overview scene in Karabo, and add a link to controls by dragging availableScenes from the configuration editor, then changing the widget to Device Scene Link, then do Configure Link, and select the controls scene.

Export the scene to SVG, convert it to a C++ function as shown above.

If you want to link to another device, make the function accept another remoteInstanceId parameter, and point to that device:

std::string sceneWithLink(const std::string& instanceId, const std::string& remoteInstanceId) {
    std::ostringstream ret;
    // SVG header
    ret << "<?xml version=\"1.0\"?>"
        << "<svg:svg xmlns:krb=\"http://karabo.eu/scene\" xmlns:svg=\"http://www.w3.org/2000/svg\" "
        << "height=\"768\" width=\"1024\" krb:version=\"2\">"
    // Link to other device's controls scene
        << "<svg:rect height=\"30\" width=\"100\" x=\"19\" y=\"101\" krb:background=\"transparent\" krb:class=\"SceneLink\" krb:font=\"\" krb:foreground=\"\" krb:frameWidth=\"0\" "
        << "krb:keys=\"" << remoteInstanceId << ".availableScenes\" krb:target=\"controls\" krb:target_window=\"dialog\" krb:text=\"CONTROLS\" krb:widget=\"DeviceSceneLink\"/>"
    // SVG footer
        << "</svg:svg>";
    return ret.str();
}

Reference Implementations

DataGenerator: provides two scenes, overview and controls with links, as described here

Beckhoff: More complex possibilites are used here.

KEP21: definition of the scene protocol