Create a Skill

Objective: Learn how to create an ArmarX skill.

Previous Tutorial: Create a "Hello World!" Component in ArmarX (C++)

Next Tutorial: Using Parameters of Skills via ARON

Reference Code: skill_tutorials

Create a new ArmarX Package

Open a terminal and create a new package:

# Create a new ArmarX package:
armarx-package init skill_tutorials

The output should look something like this:

> Creating directory ...... /code/ skill_tutorials ...
> Creating directory ...... /code/ skill_tutorials/source ...
> Creating directory ...... /code/ skill_tutorials/source/ skill_tutorials ...
> Generating .............. /code/ skill_tutorials/source/ skill_tutorials/CMakeLists.txt ...
> Creating directory ...... /code/ skill_tutorials/scenarios ...
> Generating .............. /code/ skill_tutorials/scenarios/CMakeLists.txt ...
> Creating directory ...... /code/ skill_tutorials/data ...
> Creating directory ...... /code/ skill_tutorials/data/ skill_tutorials ...
> Generating .............. /code/ skill_tutorials/data/ skill_tutorials/VariantInfo- skill_tutorials.xml ...
> Creating directory ...... /code/ skill_tutorials/etc ...
> Creating directory ...... /code/ skill_tutorials/etc/cmake ...
> Generating .............. /code/ skill_tutorials/etc/cmake/ArmarXPackageVersion.cmake ...
> Creating directory ...... /code/ skill_tutorials/etc/doxygen ...
> Creating directory ...... /code/ skill_tutorials/etc/doxygen/pages ...
> Generating .............. /code/ skill_tutorials/etc/doxygen/pages/Overview.dox ...
> Generating .............. /code/ skill_tutorials/etc/doxygen/pages/Tutorials.dox ...
> Generating .............. /code/ skill_tutorials/etc/doxygen/pages/HowTos.dox ...
> Generating .............. /code/ skill_tutorials/etc/doxygen/pages/FAQ.dox ...
> Generating .............. /code/ skill_tutorials/etc/doxygen/pages/GuiPlugins.dox ...
> Generating .............. /code/ skill_tutorials/etc/doxygen/pages/Components.dox ...
> Generating .............. /code/ skill_tutorials/etc/doxygen/mainpage.dox ...
> Generating .............. /code/ skill_tutorials/etc/CMakeLists.txt ...
> Generating .............. /code/ skill_tutorials/README.md ...
> Generating .............. /code/ skill_tutorials/LICENSE.md ...
> Generating .............. /code/ skill_tutorials/CMakeLists.txt ...
> Generating .............. /code/ skill_tutorials/.gitignore ...
> skill_tutorials package created.

Create a new ArmarX Skill

A skill in ArmarX involves a skill library as well as a skill provider.

In case you wonder what the purpose of all these different things is, and why it is not included all-in-one:

  • On a structural level:
  • The skill framework in ArmarX is a way of implementing functionalities, so that they are easy to use in other code and via a GUI. It is the abstract overall name for skill-related things in ArmarX, and can be seen as an interface and a common way to structure code.
  • A specific skill is a certain functionality, e.g., making the robot say hello. In the "skill manager GUI", available skills are listed. A "skill" is also a term on the implementation level, see below.
  • On an implementation level:
  • It might be that we want several instances of a skill to run in parallel. In some cases, they may re-use the other instances' dependencies, or in contrary, may need to be configured in a way that they do not interfer. Thus, we do not instantiate skills directly by calling their constructor, but use a skill provider for creating skill instances (like a factory). This skill provider is an ArmarX component. It should do as few things as possible, i.e., be limited to creating the skills.
  • The term skill library refers to organizing C++ code in libraries - it is the C++ library that belongs to the skill implementation. You might think of it as a subfolder or built target, defined to improve the code structure.
  • Finally, a skill is a class that contains the code to provide your functionality in the manner of the skill framework. It is usually
    • located within a skill library and
    • registered in a skill provider and
    • mentioned (e.g., regarding its name) in certain code files
  • The actual logic of your functionality is preferably not implemented in the skill itself, but in its core. The core should be independent of the skill framework, and rather be regular objects and functions that use plain C++ (and ArmarX) types. This makes it easier to re-use your functionality also outside the skill framework, and to adapt it when there are changes of the skill framework itself.

Let's first create a new skill library with the name say_hello:

# Change directory:
cd skill_tutorials
# Create a new skill library:
armarx-package add skill_library say_hello

The output should look something like this:

> Updating................. /code/ skill_tutorials/source/ skill_tutorials/CMakeLists.txt ...
> Creating directory ...... /code/ skill_tutorials/source/ skill_tutorials/say_hello ...
> Creating directory ...... /code/ skill_tutorials/source/ skill_tutorials/say_hello/test ...
> Generating .............. /code/ skill_tutorials/source/ skill_tutorials/say_hello/test/say_hello_test.cpp ...
> Creating directory ...... /code/ skill_tutorials/source/ skill_tutorials/say_hello/aron ...
> Generating .............. /code/ skill_tutorials/source/ skill_tutorials/say_hello/aron/SayHelloParams.xml ...
> Creating directory ...... /code/ skill_tutorials/source/ skill_tutorials/say_hello/constants ...
> Generating .............. /code/ skill_tutorials/source/ skill_tutorials/say_hello/constants/CMakeLists.txt ...
> Generating .............. /code/ skill_tutorials/source/ skill_tutorials/say_hello/constants/constants.h ...
> Generating .............. /code/ skill_tutorials/source/ skill_tutorials/say_hello/constants/constants.cpp ...
> Creating directory ...... /code/ skill_tutorials/source/ skill_tutorials/say_hello/core ...
> Generating .............. /code/ skill_tutorials/source/ skill_tutorials/say_hello/core/CMakeLists.txt ...
> Generating .............. /code/ skill_tutorials/source/ skill_tutorials/say_hello/core/SayHello.h ...
> Generating .............. /code/ skill_tutorials/source/ skill_tutorials/say_hello/core/SayHello.cpp ...
> Creating directory ...... /code/ skill_tutorials/source/ skill_tutorials/say_hello/skills ...
> Generating .............. /code/ skill_tutorials/source/ skill_tutorials/say_hello/skills/CMakeLists.txt ...
> Generating .............. /code/ skill_tutorials/source/ skill_tutorials/say_hello/skills/SayHello.h ...
> Generating .............. /code/ skill_tutorials/source/ skill_tutorials/say_hello/skills/SayHello.cpp ...
> Generating ..........

Take a look at everything that has been created in the source folder.

# Take a look at what has been created
cd ./source/skill_tutorials/say_hello
ls

There is a new folder named say_hello. If you open it, you can see 5 subfolders: aron, constants, core, skills, test.

  • In the aron folder you find a file SayHelloParams.xml. In this file, the set of parameters used by your skill is defined as an aron type. Representing these data by an aron type allows to easily transmit them over the network, e.g., from a GUI to the skill provider.
  • In the folder constants, all the constants used in the skill are defined.
  • In core, most of the functionality should be implemented. Implementing your code in a plain C++ library, rather than inside the skill itself, makes it easier to re-use it in other parts of ArmarX, or potentially even outside of it.
  • In the skills folder you can write all the code that is needed to connect the skill with the skill_provider.
  • In the test folder, code tests can be added.

Next we create the skill_provider, which is implemented as a component, and that can be used to call the skill from the ArmarX GUI.

cd .. # Go to the root directory of skill_tutorials.
armarx-package add skill_provider say_hello

The output should look something like this:

> Creating directory ...... /code/ skill_tutorials/source/ skill_tutorials/components ...
> Generating .............. /code/ skill_tutorials/source/ skill_tutorials/components/CMakeLists.txt ...
> Creating directory ...... /code/ skill_tutorials/source/ skill_tutorials/components/say_hello_skill_provider ...
> Generating .............. /code/ skill_tutorials/source/ skill_tutorials/components/say_hello_skill_provider/./Component.cpp ...
> Generating .............. /code/ skill_tutorials/source/ skill_tutorials/components/say_hello_skill_provider/./ComponentInterface.ice ...
> Generating .............. /code/ skill_tutorials/source/ skill_tutorials/components/say_hello_skill_provider/./Component.h ...
> Updating................. /code/ skill_tutorials/source/ skill_tutorials/components/say_hello_skill_provider/./Component.cpp ...
> Generating .............. /code/ skill_tutorials/source/ skill_tutorials/components/say_hello_skill_provider/CMakeLists.txt ...
> Updating cmake .......... /code/ skill_tutorials/source/ skill_tutorials/components/CMakeLists.txt ...
> Updating cmake .......... /code/ skill_tutorials/source/ skill_tutorials/CMakeLists.txt ...
> say_hello skill_provider element created.

Let's also take a look at what has been created here:

cd ./source/skill_tutorials/components
ls

In the components folder there is a single folder say_hello_skill_provider. Inside of that you find all your normal component files. If you don’t remember what these are all for you can take a look at the previous tutorial Create a "Hello World!" Component in ArmarX (C++). But basically here you just add the skill and later maybe define a remote GUI.

Hello World

Now you should open the package in your IDE. We will be using QtCreator, but you can use any IDE you want.

Go back to your package's root directory and start QTCreator from the terminal:

cd ~/code/skill_tut/tutorial_package
qtcreator

When QtCreator is ready, select 'Open Project' and choose CMakeLists.txt to open the project:

Open the project in QTCreator: Navigate to the directory of the created package and select the CMakeList.txt file.

Afterwards, choose the following configurations as the project's configuration and click Configure Project to continue:

Note
At the lab PCs, the configuration kit you need to use is called Axii's Kit

Project configuration: Select the configuration kit, click on the drop down menu 'Details' and deselect all boxes except 'Release with Debug Information'.

You should now see the following:

QTCreator interface

Note
In case the project tree doesn't show the files, go to Projects and select re-configure with initial parameters:

Reconfiguring the project: Navigate to the 'Projects'-menu, select 'Initial Configurattion' and click 'Re-configure with Initial Parameters.'

Firstly, open the top level CMakeList.txt file and uncomment the following line.

CMakeList.txt:

armarx_find_package(PUBLIC RobotAPI REQUIRED)
This file is part of ArmarX.

This is because the skill framework depends on code which is part of the RobotAPI built target.

Next, we want to build the project once so all the autogenerated aron files will be created. But before we can actually built the project though, we need to do two things: tell Cmake that we've created a new skill library and make this library known to our skill provider.

Therfore go to the CMakeList.txt file of the say_hello_skill_provider and add skill_tutorials::say_hello_skills to the dependencies:

Cmakelist.txt:

armarx_add_component(say_hello_skill_provider
ICE_FILES
ComponentInterface.ice
ICE_DEPENDENCIES
ArmarXCoreInterfaces
RobotAPIInterfaces
SOURCES
Component.cpp
HEADERS
Component.h
DEPENDENCIES
ArmarXCore
# skill tutorials
skill_tutorials::say_hello_skills
)

To make the skill library known to the skill provider we need to include it in the source file Component.ccp of the say_hello_skill_provider. You'll find it here: Where to find Component.cpp

Now add the following to Component.cpp:

#include <skill_tutorials/say_hello/skills/SayHello.h>

Component.cpp:

#include "Component.h"
// Include headers you only need in function definitions in the .cpp.
// #include <Eigen/Core>
// #include <SimoxUtility/color/Color.h>
// Skill libraries
// CHANGE THIS TO YOUR SKILL LIBRARY
#include <skill_tutorials/say_hello/skills/SayHello.h>
namespace skill_tutorials::components::say_hello::skill_provider
{
const std::string
Component::defaultName = "say_hello_skill_provider";
Component::createPropertyDefinitions()
{
.
.
.
IceUtil::Handle< class PropertyDefinitionContainer > PropertyDefinitionsPtr
PropertyDefinitions smart pointer type.

You can build the project now by clicking on the hammer symbol in the left bottom corner or simply press CTRL + B

Make the Skill Do Something

Now we want to have our skill do something. As mentioned before, the functionality itself goes into the core. So let's open the cpp file there and add a print "Hello World!", using the built-in ArmarX logger.

Note
In ArmarX, when we want to print something, we use the build in ArmarX logger, not the c++ cout function of the iostream library. This is because the ArmarX GUI provides a log viewer, printing the information directly to the GUI instead of an additional terminal.

In case you wonder why there is both an execute() and an _execute() method: Externally, we want to call an execute() method; i.e., the name should be written as expected. However, before we actually start the execution, we need to do some auxiliary things (here: locking the execution mutex to prevent threads running parallel from changeing an object in the memory at the same time, causing confusing behavior). As we do not want these auxiliary actions to clutter our actual implementation, we move the latter into a second method. This can be private, and we differentiate it from the first one by prefixing it with an underscore - i.e., we call it _execute(). The execute() method is now basically a wrapper around _execute() and cares about all auxiliary things that need to be done.

Where to find SayHello.cpp

core/SayHello.cpp:

#include "SayHello.h"
namespace skill_tutorials::say_hello::core
{
SayHello::SayHello(const Remote& r, const Properties& p, const Subskills& s) :
remote(r), properties(p), subskills(s)
{
}
bool SayHello::execute()
{
std::scoped_lock l(this->executionMutex);
return this->_execute();
}
bool SayHello::_execute()
{
ARMARX_INFO << "Hello World";
return true;
}
}
#define ARMARX_INFO
The normal logging level.
Definition Logging.h:181

After adding everything to your code, build your project again (press CTRL+B). Okay, great! Then let's see if we can start the skill in the ArmarX GUI.

Create a New Scenario

Firstly, you need to make the path to the build directory of your package known to ArmarX. For that, append the following lines to the armarx-workspace.rc file, which you can find in the top level directory of your current workspace. Open your files and navigate to your workspace directory and open armarx-workspace.rc and simply add:

armarx-workspace.rc:

_axii_env_set skill_tutorials_DIR "$HOME/path/to/package/build" # (e.g. "$HOME/local_projects/my_repository/build", should be outside any Axii workspace)

Now start ArmarX and open a GUI, both from the terminal.

armarx start
armarx gui

What you are now seeing on the GUI is the LogViewer, where we can see all the different messages logged by the system. Next, open the ScenarioManager by clicking on the ScenarioManager icon or simply search for it in the Widget Search.

Open the ScenarioManager: The scenario manager can be opend by clicking on the designated icon or via the Widget Search

Open your package (skill_tutorials) by clicking on Open Scenario followed by Open Package and entering the package name.

Open the package: Click on 'Open Package', enter the package name and press 'ok'

Then, create a new scenario, give it a name, and select the correct package.

Create the new Scenario: Give the Scenario a meaningfull name and select the package you have created

Any skill needs the SkillsMemory, MemoryNameSystem and the RemoteGUIProviderApp to be able to run. So add all these apps to your Scenario by clicking on Edit mode and drag the needed apps into the scenario. Don't forgett to also add your say_hello_skill_provider.

Adding the Applications: Drag and drop the applications into the Scenario

Start the scenario by clicking on the blue play button of your SayHello scenario.

Starting the Scenario: Click the blue play button of the scenario

Now open the Skills.Manager. To do so, you can search it in the Widget Search. As the proxy, SkillMemory should already be selected.

Opening the Skills.Manager: Open the Skills.Manager via the Widget search. Since the proxy should already be selected, press 'ok'

You also may want to open the Meta.LogViewer next to it, so that you can see both. Simply go to the Meta.LogViewer tap, grab the window and drag it to the left side of the GUI.

If you click update on the top left of the Skills.Manager, you should see your skill_provider appear. If you expand it, you also see the skill say_hello. In case you implement multiple skills in the library they will all appear there.

Update the Skills.Manager: To make your skill accessible, click on `Update`

Now is the time to say hello! You can click on the skill and request the execution. Take a look at the LogViewer, you should see the message you have sent. Congratulations!

Output of the LogViewer: Hello World!