Programming Guide

This section explains how a consortium customises the SLM to control its own hardware. The starting point is the example generated by the project template (ifw-templates…-ics/<prefix>slm).

Overview

You implement each loop as an execute activity and register it with the application by index. The framework provides the state machine, timers, OLDB state, error counting and status publishing; you provide the per-cycle control logic in DoExecute().

Registering a loop

In main.cpp register a factory for each loop index:

#include <ifw/slm/framework/application.hpp>
#include <myslm/MyLoop1Activity.hpp>

int main(int argc, char* argv[]) {
    ifw::slm::Application slm("MySlm", "config/myslm/config.yaml");

    slm.RegisterLoopExecuteActivityFactory(1,
        [](const std::string& name, rad::SMAdapter& sm,
           ifw::slm::DataContext& data, int idx,
           ifw::slm::LoopCompletionHandler* handler) {
            return new myslm::MyLoop1Activity(name, sm, data, idx, handler);
        });

    return slm.Run(argc, argv);
}

A plain loop

Subclass BaseLoopExecuteActivity and override DoExecute(). Return true on success and false (or throw) on failure — the framework records the reason in cycle_last_error and counts the failure:

class MyLoop2Activity : public ifw::slm::BaseLoopExecuteActivity {
public:
    using BaseLoopExecuteActivity::BaseLoopExecuteActivity;

    bool DoExecute() override {
        // Read configuration (params are strings).
        const int max_count = std::stoi(GetLoopParam<std::string>("params/max_count", std::string("10")));

        for (int i = 1; i <= max_count; ++i) {
            m_loop_state.SetValue(static_cast<double>(i));  // publish a value
            std::this_thread::sleep_for(std::chrono::seconds(1));
        }
        return true;
    }
};

Talking to a subsystem (MalRequestLoopActivity)

For loops that command another subsystem over CII/MAL, subclass the MalRequestLoopActivity<Interface> helper. It owns a rad::cii::Requestor that is created lazily from a configurable endpoint parameter (with connect and reply timeouts), so you only call Remote() and write the control logic:

class MyMotorLoop
    : public ifw::slm::MalRequestLoopActivity<fcfif::AppCmdsAsync> {
public:
    MyMotorLoop(const std::string& id, rad::SMAdapter& sm,
                ifw::slm::DataContext& data, int idx,
                ifw::slm::LoopCompletionHandler* handler)
        // endpoint read from params/motor_devmgr_endpoint
        : MalRequestLoopActivity(id, sm, data, idx, handler,
                                 "params/motor_devmgr_endpoint") {}

    bool DoExecute() override {
        try {
            auto remote = Remote();           // the FCF AppCmds proxy
            auto future = remote->Setup(/* ... */);
            future.get();
            return true;
        } catch (const std::exception& e) {
            // Connection/timeout to an absent subsystem ends up here.
            LOG4CPLUS_ERROR(m_logger, "Cannot reach " << Endpoint()
                << ": " << e.what());
            return false;
        }
    }
};

Reading configuration and OLDB

  • GetLoopParam<T>("params/<name>"[, default]) — read a per-loop configuration parameter. Parameters are stored as strings; convert numeric values.

  • ReadLoopOldb<T>("<key>", out) — read this loop’s current OLDB value (for example a setpoint written by another component); returns false if the key is missing or has a different type.

  • m_loop_state.SetValue(double) — publish the loop’s latest value to OLDB.

Behavioural notes

  • The cycle period and max_errors come from configuration; after max_errors consecutive failures the loop auto-opens.

  • A successful cycle clears the consecutive-error counter and increments cycle_nsuccess (successes since the server started).

  • Suspend pauses cycling without counting an error; Resume continues.

  • Long-running work in DoExecute() runs on a killable thread; honour cancellation by keeping cycles reasonably short or checking for interruption.