Using Subskills

Objective: Learn how to call a subskill.

Previous Tutorial: Using Parameters of Skills via ARON

Next Tutorial: none

Reference Code: skill_tutorials

Calling a skill as a subskill in ArmarX is used to avoid code duplication and easily re-use already implemented skills that are helpful to your problem. Subskills are usually solving a problem in a general manner, so that components can stay clearly arranged.

Using a Subskill

As the core::SaySomething class does not have access to callSubSkill(), and shall be independent of the skill framework, we pass a function from skills::SaySomething to core::SaySomething. For our example, this will be std::function<bool(const std::string&) sayHello as an attribute of core::SaySomething::Subskills. The function is defined in skills::SaySomething when creating the instance impl of the core::SaySomething class.

Since this explanation may have been a little fast, let's look at it step by step.

First of all, we create a new skill library and skill provider named say_something like we did in earlier tutorials. Don't forget to tell CMake and the skill provider about your new library and rearrange the order of the add-subdirectories in the first source CMakeList.txt. You can also just use the library and skill provider created back then. If you did not create them, go back to Create a Skill.

Let's begin by adding the sayHello() function, which wraps the subskill into the core::SaySomething subskills struct. Additionally, we'll include a list of names that we want to use with our subskill to the properties struct.

In core/SaySomething.h:

...
namespace skill_tutorials::say_something::core
{
class SaySomething
{
public:
/** Remote parameters of this skill implementation */
struct Remote
{
};
struct Properties
{
std::vector<std::string> namesList{"Alice!", "Bob!", "Charlie!"};
};
struct Subskills
{
std::function<bool(const std::string&)> sayHello;
};
...
};
}

Next, we add the following lines to the _execute() function in core/SaySomething.cpp, calling the sayHello() function we just defined in the subskill struct as many times as there are entries in namesList.

In core/SaySomething.cpp:

...
namespace skill_tutorials::say_something::core
{
bool SaySomething::_execute()
{
for(const auto& name : properties.namesList)
subskills.sayHello(name);
return true;
}
...
}

We add two properties to the skills::SaySomething class: useParametersSkillProviderName and useSubskill. The first one is the skill provider name of the subskill that we want to use and the second one gives us the option to disable the subskill. In this tutorial, we reuse use_parameters as a subskill but you can use any other skill.

In skills/SaySomething.h:

...
class SaySomething : public ::armarx::skills::SimpleSpecializedSkill<::skill_tutorials::say_something::arondto::SaySomethingParams>
{
public:
...
struct Properties
{
std::string useParametersSkillProviderName;
bool useSubskill;
};
};
...

Inside the skill, we have to define the wrapper for calling the subskill. To do so, in skills/SaySomething.cpp we define a lambda that takes a string as input parameter and returns a boolean:

In skills/SaySomething.cpp:

s.sayHello = [&](const std::string& text) -> bool
{
...
};

Inside, we pass the subskill's name, an ID and the modified aron parameters to the callSubskill() function. In order to use the aron parameters of our subskill we need to make them available to the skill by including them in skills/SaySomething.cpp. Afterwards we can access the parameters via skill_tutorials::use_parameters::arondto::UseParametersParams p.

In skills/SaySomething.cpp:

...
#include <skill_tutorials/use_parameters/aron/UseParametersParams.aron.generated.h>
...
SaySomething::MainResult SaySomething::main(const SpecializedMainInput& in)
{
core::SaySomething::Remote r;
core::SaySomething::Properties p;
core::SaySomething::Subskills s;
s.sayHello = [&](const std::string& text) -> bool
{
if (properties.useSubskill)
{
auto useParametersSkillProviderId =
armarx::skills::ProviderID{.providerName = properties.useParametersSkillProviderName};
auto useParametersSkillId =
armarx::skills::SkillID{.providerId = useParametersSkillProviderId, .skillName ="use_parameters"};
throwIfSkillShouldTerminate();
try{
using UseParametersParams = skill_tutorials::use_parameters::arondto::UseParametersParams;
callSubskill<UseParametersParams>(useParametersSkillId, [&text](UseParametersParams& p){
p.stringToPrint = text;
p.numPrints = 1;
});
} catch(...){
ARMARX_WARNING << "Subskill UseParameters cannot be executed.";
ARMARX_INFO << "USE PARAMETERS: " << text;
return false;
}
}
else
{
ARMARX_INFO << "USE PARAMETERS: " << text;
throwIfSkillShouldTerminate();
}
return true;
};
core::SaySomething impl(r, p, s);
if (impl.execute())
{
return MakeSucceededResult();
}
return MakeFailedResult();
}
...
#define ARMARX_INFO
The normal logging level.
Definition Logging.h:181
#define ARMARX_WARNING
The logging level for unexpected behaviour, but not a serious problem.
Definition Logging.h:193

In the last part of the main function, you can see the mysterious impl instance we mentioned in the beginning. It is an instance of the core::SaySomething class that has all the functionality we've just implemented.

Executing our Skill from the GUI

To execute our skill say_something from the ArmarX GUI, we adapt the say_something_skill_provider by adding the skill provider name of the subskill as well as the previously mentioned boolean to the properties of the component and pass them on to the skill.

In Component.h:

...
struct Properties
{
std::string useParametersSkillProviderName = "use_parameters_skill_provider";
bool useSubskill = true;
};
...

In Component.cpp:

...
::armarx::PropertyDefinitionsPtr Component::createPropertyDefinitions()
{
::armarx::PropertyDefinitionsPtr def = new ::armarx::ComponentPropertyDefinitions(getConfigIdentifier());
def->optional(properties.useParametersSkillProviderName, "useParametersSkillProviderName");
def->optional(properties.useSubskill, "useSubskill");
return def;
}
...
void Component::onConnectComponent()
{
namespace skills = ::skill_tutorials::say_something::skills;
skills::SaySomething::Remote r;
skills::SaySomething::Properties p;
p.useParametersSkillProviderName = properties.useParametersSkillProviderName;
p.useSubskill = properties.useSubskill;
addSkillFactory<skills::SaySomething>(r, p);
}
...
IceUtil::Handle< class PropertyDefinitionContainer > PropertyDefinitionsPtr
PropertyDefinitions smart pointer type.
This file is part of ArmarX.

Don't forget to build your project when you have implemented everything!

Running the Skill in ArmarX GUI

Now, you can open the ArmarX GUI and try out your new skills! Start the skill provider and its dependencies in the ScenarioManager. Note, that you also have to add the skill providers for your subskills. Then, you should see the skill in the Skills.Manager and be able to execute it: LogViewer Output: When executing your skill, the output can be observed in the LogViewer

Awesome, you did great! If you want to put your newly acquired skill knowledge to the test, try modifying the say_something skill in a way that you can use the GUI to set the values of the properties that will be printed by the subskill. Hint: look at the last tutorial again.