Skip to content

iit-DLSLab/robotlib

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

749 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Robotlib

Overview

Robotlib is a modular and generic robot software interface that allows the same locomotion framework to run on robots with different morphologies. It was implemented within the project ANT as a generic software module to interface with robots having different morphologies: the quadruped Aliengo and the hexapod Crex. For more detail about the project, read the paper Towards a generic navigation and locomotion control system for legged space exploration.

Robotlib is based on a factory design pattern that allows the creation of objects (i.e. robot objects) without exposing it to the client, leading to a modular architecture, plug-in based, exploiting polymorphisms and dlopen API. It provides a templated robot morphology that defines the hierarchical structure of limbs with associated kinematic and dynamic data. Additionally, it provides virtual and utility functions for the robot kinematics, dynamics, and jacobians. Each robot-specific library inherits the Robotlib interface to implement the particular morphology, kinematics, and dynamics of each different robot. Thanks to polymorphisms, it is possible to use the interface to access the deepest robot specific implementation of functions declared or defined in Robotlib.

However, polymorphisms per se is not enough to achieve a plug-in based architecture. Here is why dlopen API comes in handy. The dlopen API, allows to dynamically load shared libraries representing the concrete implementation of the interface, i.e. robot specific libraries. This is achieved by defining 2 class factory functions inside the interface, defined as extern "C" to avoid name mangling. One function creates a class instance, the other one allows to destroy it.

With this architecture you just need to:

  • Write your controller based on Robotlib
  • Implement the glue code, that is the robot specific libraries
  • Loading at runtime the robot library associated to the robot you want to control

Through the common interface, it is therefore possible to keep one single controller implementation for controlling robots with different morphologies.

In the image below you can see an overview of how Robotlib and the robot specific libraries are integrated in a controller framework. Here Aliengolib and Crexlib are the glue codes respectively of Aliengo and Crex.

Robotlib

Notice that the robot states are decoupled from the hierarchical robot structure. This means that a robot object does not store any robot state like joint configuration, joint velocities, stance status per leg etc. It provides instead data structures that can be used to define robot states when implementing a controller.

In the following an overview of Robotlib main classes is provided:

  • RobotFactory: this class stores the static function openRobot used to load at runtime the glue code
  • RobotBase: this is the main class used as interface to the glue code. It provides data structures such as LimbDataMap, JointDataMap, JointState and Jacobian classes. It also provides the hierarchical structure of limbs as a sequence of joints and links together with utility functions like forwardKinematics, inverseKinematics and inverseDynamics
  • LimbBase: this is the class to interface with any kind of limb, no matter if it is a leg or arm
  • Frame: this class represents a generic frame, no matter if it is a joint or a link

For a complete overview of the inheritance graph, you can check the class hierarchy generated by doxygen (see section Documentation).

There are several advantages of using an architecture abstraction layer like Robotlib. For example, with Robotlib, robot-specific structures are hidden from the controllers and state estimators, making them more modular and easier to implement. The structure allows controllers and state estimators to be written only once, and then the framework can dynamically load different robots. The abstraction layer also provides an easy way to switch backend libraries that compute the kinematics and dynamics without affecting the rest of the framework. Robotlib is written in C++17 to be fast and portable. It is compatible with the most adopted robotics libraries and is real-time safe.


Authors in alphabetical order: Gianluca Cerilli, Geoff Fink and Marco Marchitto

Installation

Pull image

Pull the docker image ghcr.io/iit-dlslab/dls2-dev:latest.

Building

Open docker image

docker run -it --rm \
  --name dls_container \
  --hostname docker \
  --gpus all \
  --privileged \
  --network host \
  -e DISPLAY="$DISPLAY" \
  -e DLS=2 \
  -v /tmp/.X11-unix:/tmp/.X11-unix:rw \
  -v "$PWD:$PWD" \
  -w "$PWD" \
  ghcr.io/iit-dlslab/dls2-dev:latest \
  /bin/bash

Build

To build Robotlib, clone the latest version of this repository and compile the package using

git clone git@gitlab.advr.iit.it:dls-lab/robotlib.git

cd robotlib

mkdir build

cd build

cmake .. -DCMAKE_BUILD_TYPE=Release

make install

Usage

Before using Robotlib, you need to install the glue code associated to the robot you want to control. For example, if you want to control the Aliengo quadruped robot you can follow the instructions here to install its glue code. Essentially, to install a glue code you just need to compile it with make install.

If you don't want to install any glue code but you just want to play aroud with Robotlib you can use one of the dummy robots defined in Robotlib: a dummy quadruped or a dummy hexapod. Their (dummy) glue code is already installed when you install Robotlib.

We can now have a look at an example of using Robotlib. First of all we need to create a robot object, loading at run time the shared library associated to the robot we want to control

// Instantiate the robot object
std::shared_ptr<robotlib::RobotBase> robot {robotlib::RobotFactory::openRobot("dummy-quadruped")};

With the openRobot function, the shared library associated to the dummy quadruped robots is loaded at runtime. The real object type is masquerade by the Robotlib interface, and therefore the robot object is used to get robot information and data structures and to use utility functions.

If you wanted to load the aliengo glue code, you just would need to install it and then you could load it with

// Instantiate the Aliengo robot object
std::shared_ptr<robotlib::RobotBase> aliengo {robotlib::RobotFactory::openRobot("aliengolib")};

Some robot information can be accessed through the robot object, for example

// Print some robot information
std::cout << robot->getName() << std::endl;
std::cout << robot->getNLEGS() << std::endl;
std::cout << robot->getNARMS() << std::endl;
std::cout << robot->getNLIMBS() << std::endl;
std::cout << robot->getNJOINTS() << std::endl;

Suppose now that you want a variable storing the stance status of each leg. You can define a leg data map object in this way

// Define and populate a leg data map object
robotlib::LimbDataMap<bool> stance_status {robot->makeLimbDataMap<bool>(false)}; // or auto stance_status {robot->makeLimbDataMap<bool>(false)};

LimbDataMap<Data> is a type alias for std::map<LimbPtr, Data>. Each entry associates a shared pointer to a limb with a value of type Data. You can construct one directly or use the makeLimbDataMap helper on a RobotBase object, which initialises all limb entries for you.

Let's now populate the stance_status variable

for (auto& leg : robot->getLegs())
{
    stance_status[leg] = true;
    std::cout << leg->getName() << " leg, stored data: " << stance_status[leg] << std::endl;
}

As you can see in the code above, we use iterators to iterate over a set of legs got from the robot object. In Robotlib you can access to data by a shared pointer to the limb. For example, if you want to access to the stance status associated to the left front leg you can do it as follows

bool stance {stance_status[robot->getLimb("LF")]};

where "LF" is the name associated to the left front leg. The getLimb function returns a std::shared_ptr<LimbBase> object that is used as the key to access the associated data stored in stance_status.

As another example of data type that can be associated to legs consider the following example

// Define and populate a leg data map object of jacobians; each jacobian is associated to a leg
robotlib::LimbDataMap<robotlib::Jacobian> feet_jacobian{robot->makeFeetJacobian()}; // or auto jacobian{robot->makeFootJacobian(leg)};
for (auto& leg : robot->getLegs())
{
    std::cout << "Foot jacobian per " << leg->getName() << " leg: " << std::endl;
    feet_jacobian[leg] << 1, 0, 0,
        0, 1, 0,
        0, 0, 1,
        1, 0, 0,
        0, 1, 0,
        0, 0, 1;
    std::cout << feet_jacobian[leg] << std::endl;
}

In this example we have used the function makeFeetJacobian to create a Jacobian object (6 × n_joints_leg) for each leg. To create a jacobian for a single leg you can use makeFootJacobian(leg), where leg is a LimbPtr.

Consider now the following example to compute the forward kinematics for each leg

// Forward kinematics
robotlib::JointState q_input{robot->makeJointState(0.0)};
robotlib::LimbDataMap<Eigen::Vector3d> foot_position{robot->makeLimbDataMap<Eigen::Vector3d>(Eigen::Vector3d::Zero())};
robot->forwardKinematics(q_input, foot_position);

The forwardKinematic function takes as input a joint configuration and overwrite the foot_position variable after having computed the forward kinematics. This is an example of virtual function declared in the RobotBase class, whose implementation is defined in the glue code. Thanks to opendl API and polymorphisms, it is possible to access to its implementation through the Robotlib interface.

Finally, you can run an executable to show robot information. The executable is called robot_info and it is generated in the build/src folder after building Robotilb. The executable can be run as follows

./robot_info <robot_library> --info

For example

./robot_info dummy-quadruped --info

or in case of Aliengo robot

./robot_info aliengolib --info

where aliengolib is the name of the installed glue code library for Aliengo.

A final remark about the openRobot funtion. When it takes only one argument, this function calls the createRobot_t factory function defined in the glue code to create the robot object. This function may or may not use an urdf as source of kinematic and dynamic robot information. But the openRobot function can also take a second argument identyfing the robot urdf in string format.

Writing a glue code

The implementation of the glue code is quite arbitrary. You can use whatever tool you want to compute the kinematics and the dynamics of the robot (like for example RobCoGen or Pinocchio). As an example, see pinocchio-gluecode.

Remember that you access to glue code functions through Robotlib. So if you define a new function in the glue code that it is not in Robotlib, it cannot be called by a RobotBase (or LimbBase) object. You would need then to make it visible also in Robotlib.

Documentation

The Robotlib documentation is written using Doxygen. To generate the documentation go in the folder doc and execute the following command

doxygen robotlib_doxygen.conf

Latex and html files will be generated according to the instructions provided in the configuration file robotlib_doxygen.conf.

To access to the html documentation, just double click on the file index.html stored in the folder doc/html: it will open the file in your browser.

To view the inheritance graph, once the html file is opended in your browser, go in the Classes section and click on the Class Hierarchy tab.

For other examples, you can have look at the tests provided in the tests folder.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages