Programming Guide

Device Manager Extensions

Having type safe interfaces as defined by CII ICD XML enables to improve runtime robustness of our applications. However it is also makes the extendability harder to achieve. To simplify adding instrument specific extensions to the Device Manager, we have provided a template that can be used to create a special device along with a custom server and some companion modules.

The template, that is located under fcf/template/project, generates a complete WAF project. This project can be built, tested and executed after the generation. It is aimed to be used by Consortia developers to build their own FCS. For details how to use it, please refer to section Getting Started Creating a FCS Configuration.

Template

The template to generate the code and the configuration is based on the CookieCutter tool (Cookiecutter documentation).

$ cookiecutter ifw-hl/fcf/templates/project

Top Directory Structure

The first level of the generated directory contains the main packages for an instrument. For the moment, it includes only a FCS. Later we will add other packages to the template according to the readiness of the components.

<root>             # Generated WAF project
├── <component>    # directory containing the generated FCF component.
└── wscript        # WAF build script

Component Directory Structure

The generated structure resembles the structure of the FCF component.

<component>             # Generated FCS component
├── common              # Custom ActionMgr and ActionSetup classes (RAD based)
├── cppclient           # Custom C++ client interface
├── devices             # Custom Device
├── devsim              # Simulator for custom device
├── <cii_if>            # Custom CII XML ICD
├── server              # Custom server
├── LCS/plcprj1         # Example PLC Project (VS)
└── wscript

Interface

We are assuming a typical case where you need to define a new device or change the interface of an existing one. The generated interface is just a dummy example, developers will need to fill it up according to real requirements.

Re-defintion of the FcsUnion

For a given device name, in this case mirror, the generated FcsUnion is shown below.

<union name="FcsUnion">
    <discriminator type="nonBasic" nonBasicTypeName="AppDeviceType" />
     <case>
        <caseDiscriminator value ="MIRROR"/>
            <member name="mirrorData" type="nonBasic" nonBasicTypeName="Mirror" />
    </case>
</union>

Dummy Device Structure

For a given device name, in this case mirror, the generated device structure is shown below.

<enum name="ActionMirror">
    <enumerator name="PING"/>
</enum>

<!-- TODO: Add here custom device definition -->
<!-- TODO: Device might derive from an existing one -->
<struct name="Mirror">
    <member name="id" type="string" />
    <!-- TODO: Add here actions of the custom device -->
    <member name="action" type="nonBasic" nonBasicTypeName="ActionMirror"/>
</struct>

State Machine Definition

It is assumed no changes are needed in the state machine of the custom Device Manager. However in order to process correctly the events for the new interface, it is needed to add a new transition (CustomEvents.Setup). This is achieved by appending a SCXML file to the standard one used by the Device Manager.

<!-- Custom Setup SM -->
<scxml xmlns="http://www.w3.org/2005/07/scxml" xmlns:customActionDomain="http://my.custom-actions.domain/CUSTOM"
   version="1.0"
   initial="On">
    <state id="On">
        <state id="Operational">
            <state id="Idle">
                <transition event="CustomEvents.Setup">
                    <customActionDomain:ActionsCustomSetup.Start name="ActionsCustomSetup.Start"/>
                </transition>
            </state>
            <state id="Error">
                <transition event="CustomEvents.Setup">
                    <customActionDomain:ActionsCustomSetup.Start name="ActionsCustomSetup.Start"/>
                </transition>
            </state>
        </state>
    </state>
</scxml>

This is added to the state machine engine by calling the Method Append, see the example below.

state_machine.Append("fcs1/server/custom_setup.xml", &action_mgr.GetActions(),
                    &action_mgr.GetActivities());

Warning

It is not possible to overwrite the default SCXML file so if more changes are needed, users shall use a new SCXML file.

The state machine engine shall also add an event listener to the custom action, see the example below.

state_machine.AddEventListener(actionsCustom);

CII MAL Interfaces

In order to reuse the existing Device Manager interfaces, the CII Server shall aggregates the two interfaces: the existing one (fcfif) and the custom one, see the example below.

malReplier.RegisterService<fcfif::AsyncStdCmds>("StdCmds",
                    std::make_shared<fcf::devmgr::common::StdCmdsImpl>(state_machine));
malReplier.RegisterService<myif::AsyncCustomCmds>("CustomCmds",
                    std::make_shared<fcs1::common::CustomCmdsImpl>(state_machine));

Custom Device

The generated code includes the implementation of a dummy custom device that can be used as starting point to develop instrument specific ones. This device contains the intelligence to deserialize the custom setup command in method Setup. Users will have to modify this method and method IsSetupActive to adapt to further modifications.

void Mirror::Setup(const std::any& payload) {
    RAD_LOG_TRACE();

    if (!m_config->GetIgnored()) {
        auto fcf_vector = std::any_cast<std::vector<std::shared_ptr<myif::FcsUnion>>>(&payload);
            for (auto it = fcf_vector->begin();
                it != fcf_vector->end(); it++) {
                    auto fcs_union = *it;
            if (fcs_union->getDiscriminator() != ::myif::AppDeviceType::MIRROR)
                continue;

            auto mirror = fcs_union->getMirrorData();

            RAD_LOG_DEBUG() << "[" << m_config->GetName() << "] "
                      << "Setup ID: " << mirror->getId();

            // ignore other devices
            if (IsMsgForMe(mirror->getId()) != true)
                continue;

            //@TODO: Add handling of setup parameters
            RAD_LOG_INFO() << "[" << m_config->GetName() << "] "
                      << "Setup Action: " << mirror->getAction();

            if (mirror->getAction() == ::myif::ActionMirror::PING) {
                RAD_LOG_INFO() << "[" << m_config->GetName() << "] "
                         << "Executing RPC_PING ...";
                m_lcs_if->Ping();
                RAD_LOG_INFO() << "[" << m_config->GetName() << "] "
                         << "Successful call of Mirror ping: ";
            }
        }
    }
}

Testing

The generated device module contains a set unit test that can be extended accordingly. After the generation, all unit test shall pass based on the actual implementation.

$ cd <component>devices
$ waf test --alltests
[==========] Running 26 tests from 3 test cases.
[----------] Global test environment set-up.
[----------] 9 tests from TestMirror
[ RUN      ] TestMirror.Ctor
...
[----------] Global test environment tear-down
[==========] 26 tests from 3 test cases ran. (2545 ms total)
[  PASSED  ] 26 tests.

Custom Simulation

In oder to allow the testing of the dummy device, a device simulator is generated and can be used for testing purposes. The simulator implements the RPC_Ping dummy method used by the custom device as an action of the Setup command.

Restrictions

Warning

The custom server does not allow to combine standard and custom devices. All custom devices shall use dedicated custom servers, well in case these custom devices really change the interface.