Creating a Simple RTC Component¶
This tutorial shows how to create and run a simple, custom RTC Component application that is using the Runnable State Machine from the Component Framework.
Note
General information about RTC Components can be found in section RTC Component and in the RTC Toolkit design document.
The basic idea is to let users provide a custom Business Logic class that defines the behavior for the different stages of the component’s life-cycle (i.e. it implements the activity methods).
The example component can be instantiated (started) several times using different component
instance names and settings. As every other RTC Component, the application can be steered using
the toolkit-provided rtctkClient application.
To make life easier for developers a working example is already provided with the RTC Toolkit
in directory _examples/exampleRtcComponent
The example is composed into the following waf modules:
app- The Example RTC Component application including default configurationscripts- Helper scripts for deployment and control
Running the Example¶
After installing the RTC Toolkit (see Installation) the executables of the working example
should be present in $INTROOT/bin and they should already be globally available via $PATH.
To run the example simply use the following command:
rtctkExampleComponent.sh run
The example will bring up two RTC component instances and let them step through their life-cycles.
You can follow the output on console or tail -f individual log files in $INTROOT/logsink.
After about 35 seconds the example will terminate gracefully.
To step manually through the component life-cycle use the following sequence of commands:
# Deploy and start the example applications
rtctkExampleComponent.sh deploy
rtctkExampleComponent.sh start
# Use the client to step through the life-cycle of the respective component instance
rtctkExampleComponent.sh send rtc_component_1 Init
rtctkExampleComponent.sh send rtc_component_1 Enable
rtctkExampleComponent.sh send rtc_component_1 Run
rtctkExampleComponent.sh send rtc_component_1 Idle
rtctkExampleComponent.sh send rtc_component_1 Disable
rtctkExampleComponent.sh send rtc_component_1 Reset
# Gracefully terminate the applcations and clean-up
rtctkExampleComponent.sh stop
rtctkExampleComponent.sh undeploy
In the deploy phase the application environment is prepared by resolving environment variables
and by copying certain YAML files into the respective run-directory in $INTROOT/run/.
Then in phase start the respective applications are started with the correct command line
arguments. While the applications are running they can be steered through their life-cycles using
commands like Init, Enable, Run, etc.
In the stop and undeploy phases the application processes are terminated gracefully and
the run-directory is cleared again.
Note
The example shell scripts were introduced to make running rtctk applications simpler, to hide complexity and to fake functionality that is not yet provided by the CII. They are very likely to be changed or removed at some later point.
Development Guide¶
This section explains how to create a simple RTC Component application from scratch.
A minimal RTC Component application consists of the following parts:
Build Script
Business Logic Class
main.cpp
Config Files
Build Script¶
The wscript contains rules how to build the example component application.
from wtools.module import declare_cprogram
declare_cprogram(target='rtctkExampleComponent', use='componentFramework.rtcComponent')
Note
To understand and work with waf build scripts basic knowledge on waf and
wtools is required.
Business Logic Class¶
A custom Business Logic class implements the respective life-cycle interface. In the provided
example this class is called ExampleBusinessLogic.
#include "rtctk/componentFramework/runnableStateMachineLogic.hpp"
#include "rtctk/componentFramework/serviceContainer.hpp"
namespace rtctk::exampleComponent {
class ExampleBusinessLogic : public rtctk::componentFramework::RunnableStateMachineLogic
{
public:
ExampleBusinessLogic(const std::string& name,
rtctk::componentFramework::ServiceContainer& services);
virtual ~ExampleBusinessLogic() = default;
// activities
void Starting(rtctk::componentFramework::StopToken st) override;
void Initialising(rtctk::componentFramework::StopToken st) override;
void Enabling(rtctk::componentFramework::StopToken st) override;
void Disabling(rtctk::componentFramework::StopToken st) override;
void GoingRunning(rtctk::componentFramework::StopToken st) override;
void GoingIdle(rtctk::componentFramework::StopToken st) override;
void Running(rtctk::componentFramework::StopToken st) override;
void Recovering(rtctk::componentFramework::StopToken st) override;
void Updating(rtctk::componentFramework::StopToken st,
rtctk::componentFramework::Payload args) override;
// guards
bool IsUpdatingAllowed(rtctk::componentFramework::Payload args) override;
};
} // closing namespace
The listing above shows the file exampleBusinessLogic.hpp with the user-provided class
ExampleBusinessLogic that implements the RunnableStateMachineLogic interface class.
In the corresponding implementation file exampleBusinessLogic.cpp important contextual
information and services, such as the component instance name or a handle to the Runtime Repository
are passed to the Business Logic via the constructor.
ExampleBusinessLogic::ExampleBusinessLogic(const std::string& name, ServiceContainer& services)
: RunnableStateMachineLogic(name, services)
{
// retrieve handle to the runtime repository adapter from the service container
auto& rtr = m_services.Get<RuntimeRepoApiIf>();
// use the runtime repository adapter to manipulate datapoints
DataPointPath dp_name = DataPointPath("/" + name + "/my_dp");
rtr.CreateDataPoint(dp_name, "RtcString");
rtr.SetDataPoint<std::string>(dp_name, "42");
}
Individual life-cycle methods can be implemented by toolkit users in order to provide custom
behavior. Logging functionality is globally available when including
rtctk/componentFramwork/logger.hpp.
Here an example implementation for activity Running:
void ExampleBusinessLogic::Running(StopToken st)
{
while(not st.StopRequested()) {
LOG4CPLUS_INFO(GetLogger(), "... still Running");
sleep_for(1s);
}
}
main.cpp¶
In main.cpp the RTC Component is set up and connected with the respective Business Logic class.
To run the Business Logic as an RTC Component main.cpp must be implemented in a specific way:
#include "rtctk/componentFramework/rtcComponent.hpp"
#include "exampleBusinessLogic.hpp"
using rtctk::exampleComponent::ExampleBusinessLogic;
using namespace rtctk::componentFramework;
void RtcComponentMain(Args const& args)
{
RunAsRtcComponent<ExampleBusinessLogic>(args);
}
RtcComponentMain()is the main entry point for user code. It is called bymain()which is owned by the toolkit to be able to do arbitrary work before and after user code is being executed.RunAsRtcComponent<T>()is a template function that executes the specified Business Logic as an RTC Component, it will construct the Business Logic class and then call the respective life-cycle methods on command reception.
To provide more flexibility function RunAsRtcComponent() can also take a business logic
factory method as an optional argument. This sort of dependency injection allows defining a
Business Logic class with a custom constructor, which may be useful for separation of concerns
and testing.
Note
The provided example code shows different ways how the Business Logic class can be customised
using e.g. dependency injection, inheritance or templates. The examples can be activated by
uncommenting the respective #define in main.cpp.
Config Files¶
To be able to instantiate and run the component it also needs some initial configuration.
Currently such configuration is provided using yaml files.
Here an example of the service discovery configuration file service_disc.yaml.
common:
runtime_repo_endpoint:
type: RtcString
value: file:$REPO_DIR/runtime_repo/
oldb_endpoint:
type: RtcString
value: file:$REPO_DIR/oldb.yaml
rtc_component_1:
req_rep_endpoint:
type: RtcString
value: zpb.rr://127.0.0.1:12081/
pub_sub_endpoint:
type: RtcString
value: zpb.ps://127.0.0.1:12082/
rtc_component_2:
req_rep_endpoint:
type: RtcString
value: zpb.rr://127.0.0.1:12083/
pub_sub_endpoint:
type: RtcString
value: zpb.ps://127.0.0.1:12084/
More information about service discovery can be found in section Service Discovery.
In case the component also makes use of the Runtime Repository or the OLDB further yaml files
may be required to create data points initially. See also Runtime Configuration Repository.