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); returnsfalseif 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_errorscome from configuration; aftermax_errorsconsecutive failures the loop auto-opens.A successful cycle clears the consecutive-error counter and increments
cycle_nsuccess(successes since the server started).Suspendpauses cycling without counting an error;Resumecontinues.Long-running work in
DoExecute()runs on a killable thread; honour cancellation by keeping cycles reasonably short or checking for interruption.