From adbb769f257dcd64314e764d03aab168ef8e9e10 Mon Sep 17 00:00:00 2001
From: MaximilienNaveau <maximilien.naveau@gmail.com>
Date: Wed, 8 Dec 2021 15:11:32 +0100
Subject: [PATCH] Huge commit: - split ros1 and ros2 packaging - unit-test on
 ros1 noetic and melodic - unit-test on ros2 foxy

---
 .github/workflows/ci_ubuntu18_04_ros1.yml     |  36 +++
 .github/workflows/ci_ubuntu20_04_ros1.yml     |  36 +++
 .github/workflows/ci_ubuntu20_04_ros2.yml     |  36 +++
 CMakeLists.txt                                |   2 +
 ros1/CMakeLists.txt                           |   2 +
 ros2/CMakeLists.txt                           |  19 +-
 .../ros_python_interpreter_server.hpp         |  10 +-
 .../dynamic_graph_bridge/ros_sot_loader.hpp   |   0
 .../dynamic_graph_bridge/sot_loader.hh        | 111 +++++++
 .../dynamic_graph_bridge/sot_loader_basic.hh  | 131 ++++++++
 ros2/scripts/remote_python_client.py          |   6 +-
 ros2/scripts/run_command                      |   1 +
 ros2/src/CMakeLists.txt                       | 182 ++++++++++--
 ros2/src/dynamic_graph/ros/__init__.py        |   2 +-
 ros2/src/dynamic_graph/ros/dgcompleter.py     |   4 +-
 ros2/src/dynamic_graph/ros/ros.py             |   6 +-
 ros2/src/programs/geometric_simu.cpp          |  11 +-
 ros2/src/ros.cpp                              |   6 +-
 ros2/src/ros_publish-python-module-py.cc      |  12 +-
 ros2/src/ros_publish.cpp                      |   3 +-
 ros2/src/ros_publish.hpp                      |   2 +-
 ros2/src/ros_publish.hxx                      |   2 +-
 ros2/src/ros_python_interpreter_client.cpp    |   4 +-
 ros2/src/ros_sot_loader.cpp                   |   0
 ros2/src/ros_subscribe-python-module-py.cc    |  11 +-
 ros2/src/ros_subscribe.cpp                    |   5 +-
 ros2/src/ros_subscribe.hpp                    |   2 +-
 ros2/src/ros_subscribe.hxx                    |   2 +-
 ros2/src/sot_loader.cpp                       | 281 ++++++++++++++++++
 ros2/src/sot_loader_basic.cpp                 | 249 ++++++++++++++++
 ros2/tests/CMakeLists.txt                     |  77 +++--
 .../impl_test_sot_external_interface.cpp      |  77 +++++
 .../tests/impl_test_sot_external_interface.hh |  39 +++
 .../launching_test_sot_loader.cpython-38.pyc  | Bin 0 -> 2062 bytes
 ...ching_test_sot_loader_basic.cpython-38.pyc | Bin 0 -> 2103 bytes
 .../tests/launch/launching_test_sot_loader.py |  78 +++++
 .../launch/launching_test_sot_loader_basic.py |  74 +++++
 ros2/tests/main.cpp                           |  24 ++
 ros2/tests/python_scripts/simple_add.py       |   1 +
 ros2/tests/python_scripts/simple_add_fail.py  |   1 +
 ros2/tests/test_common.hpp                    | 167 +++++++++++
 ros2/tests/test_import.py                     |  27 ++
 ros2/tests/test_ros_init.cpp                  | 191 ++++++++++++
 ros2/tests/test_ros_interpreter.cpp           | 272 +++++++++++++++++
 ros2/tests/test_sot_loader.cpp                |  93 ++++++
 ros2/tests/test_sot_loader_basic.cpp          | 184 ++++++++++++
 ros2/tests/urdf/dgb_minimal_robot.urdf        |  98 ++++++
 47 files changed, 2466 insertions(+), 111 deletions(-)
 create mode 100644 .github/workflows/ci_ubuntu18_04_ros1.yml
 create mode 100644 .github/workflows/ci_ubuntu20_04_ros1.yml
 create mode 100644 .github/workflows/ci_ubuntu20_04_ros2.yml
 delete mode 100644 ros2/include/dynamic_graph_bridge/ros_sot_loader.hpp
 create mode 100644 ros2/include/dynamic_graph_bridge/sot_loader.hh
 create mode 100644 ros2/include/dynamic_graph_bridge/sot_loader_basic.hh
 create mode 120000 ros2/scripts/run_command
 delete mode 100644 ros2/src/ros_sot_loader.cpp
 create mode 100644 ros2/src/sot_loader.cpp
 create mode 100644 ros2/src/sot_loader_basic.cpp
 create mode 100644 ros2/tests/impl_test_sot_external_interface.cpp
 create mode 100644 ros2/tests/impl_test_sot_external_interface.hh
 create mode 100644 ros2/tests/launch/__pycache__/launching_test_sot_loader.cpython-38.pyc
 create mode 100644 ros2/tests/launch/__pycache__/launching_test_sot_loader_basic.cpython-38.pyc
 create mode 100644 ros2/tests/launch/launching_test_sot_loader.py
 create mode 100755 ros2/tests/launch/launching_test_sot_loader_basic.py
 create mode 100644 ros2/tests/main.cpp
 create mode 100644 ros2/tests/python_scripts/simple_add.py
 create mode 100644 ros2/tests/python_scripts/simple_add_fail.py
 create mode 100644 ros2/tests/test_common.hpp
 create mode 100755 ros2/tests/test_import.py
 create mode 100644 ros2/tests/test_ros_init.cpp
 create mode 100644 ros2/tests/test_ros_interpreter.cpp
 create mode 100644 ros2/tests/test_sot_loader.cpp
 create mode 100644 ros2/tests/test_sot_loader_basic.cpp
 create mode 100644 ros2/tests/urdf/dgb_minimal_robot.urdf

diff --git a/.github/workflows/ci_ubuntu18_04_ros1.yml b/.github/workflows/ci_ubuntu18_04_ros1.yml
new file mode 100644
index 0000000..d5740dd
--- /dev/null
+++ b/.github/workflows/ci_ubuntu18_04_ros1.yml
@@ -0,0 +1,36 @@
+name: Ubuntu18.04, ROS1 Continuous Integration
+
+on:
+  push:
+    branches:
+    - master
+    - devel
+  pull_request:
+    branches:
+    - master
+    - devel
+
+jobs:
+  build:
+    runs-on: ubuntu-18.04
+    steps:
+      #
+      # Setup the machines and build environment
+      #
+      - name: Install ROS.
+        uses: ros-tooling/setup-ros@v0.2
+        with:
+          required-ros-distributions: melodic
+
+      #
+      # Checkout the current branch
+      #
+      - uses: actions/checkout@v2
+
+      #
+      # Build and test the repo
+      #
+      - uses: ros-tooling/action-ros-ci@v0.2
+        with:
+          package-name: dynamic_graph_bridge_msgs
+          target-ros1-distro: melodic
diff --git a/.github/workflows/ci_ubuntu20_04_ros1.yml b/.github/workflows/ci_ubuntu20_04_ros1.yml
new file mode 100644
index 0000000..210afd4
--- /dev/null
+++ b/.github/workflows/ci_ubuntu20_04_ros1.yml
@@ -0,0 +1,36 @@
+name: Ubuntu20.04, ROS1 Continuous Integration
+
+on:
+  push:
+    branches:
+    - master
+    - devel
+  pull_request:
+    branches:
+    - master
+    - devel
+
+jobs:
+  build:
+    runs-on: ubuntu-20.04
+    steps:
+      #
+      # Setup the machines and build environment
+      #
+      - name: Install ROS.
+        uses: ros-tooling/setup-ros@v0.2
+        with:
+          required-ros-distributions: noetic
+
+      #
+      # Checkout the current branch
+      #
+      - uses: actions/checkout@v2
+
+      #
+      # Build and test the repo
+      #
+      - uses: ros-tooling/action-ros-ci@v0.2
+        with:
+          package-name: dynamic_graph_bridge_msgs
+          target-ros1-distro: noetic
diff --git a/.github/workflows/ci_ubuntu20_04_ros2.yml b/.github/workflows/ci_ubuntu20_04_ros2.yml
new file mode 100644
index 0000000..237cf8c
--- /dev/null
+++ b/.github/workflows/ci_ubuntu20_04_ros2.yml
@@ -0,0 +1,36 @@
+name: Ubuntu20.04, ROS2 Continuous Integration
+
+on:
+  push:
+    branches:
+    - master
+    - devel
+  pull_request:
+    branches:
+    - master
+    - devel
+
+jobs:
+  build:
+    runs-on: ubuntu-20.04
+    steps:
+      #
+      # Setup the machines and build environment
+      #
+      - name: Install ROS.
+        uses: ros-tooling/setup-ros@v0.2
+        with:
+          required-ros-distributions: foxy
+
+      #
+      # Checkout the current branch
+      #
+      - uses: actions/checkout@v2
+
+      #
+      # Build and test the repo
+      #
+      - uses: ros-tooling/action-ros-ci@v0.2
+        with:
+          package-name: dynamic_graph_bridge_msgs
+          target-ros2-distro: foxy
diff --git a/CMakeLists.txt b/CMakeLists.txt
index ee63a29..5daa8b0 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -34,6 +34,8 @@ PROJECT(${PROJECT_NAME} ${PROJECT_ARGS})
 if(DEFINED ENV{ROS_VERSION} )
   if ($ENV{ROS_VERSION} EQUAL 2) # if ROS2
     add_subdirectory(ros2)
+    #ROS 2 packaging
+    ament_package()
   else()
     add_subdirectory(ros1)
   endif()
diff --git a/ros1/CMakeLists.txt b/ros1/CMakeLists.txt
index 87041c6..a80c75f 100644
--- a/ros1/CMakeLists.txt
+++ b/ros1/CMakeLists.txt
@@ -74,3 +74,5 @@ install(PROGRAMS
   DESTINATION share/${PROJECT_NAME}
   )
 
+# we also need to install the header files
+install(DIRECTORY include/ DESTINATION include)
diff --git a/ros2/CMakeLists.txt b/ros2/CMakeLists.txt
index 6e0fc75..4e8f2ac 100644
--- a/ros2/CMakeLists.txt
+++ b/ros2/CMakeLists.txt
@@ -37,18 +37,11 @@ IF(BUILD_PYTHON_INTERFACE)
 ENDIF(BUILD_PYTHON_INTERFACE)
 
 add_subdirectory(src)
-# add_subdirectory(tests)
+add_subdirectory(tests)
 
 #install ros executables
-install(PROGRAMS
-  scripts/robot_pose_publisher
-  scripts/run_command
-  scripts/tf_publisher
-  DESTINATION share/${PROJECT_NAME}
-  )
-
-# Install package information
-install(FILES package.xml DESTINATION share/${PROJECT_NAME})
-
-#ROS 2 packaging
-ament_package()
+install(PROGRAMS scripts/remote_python_client.py  RENAME remote_python_client DESTINATION lib/${PROJECT_NAME})
+install(PROGRAMS scripts/remote_python_client.py  RENAME run_command DESTINATION lib/${PROJECT_NAME})
+
+# we also need to install the header files
+install(DIRECTORY include/ DESTINATION include)
diff --git a/ros2/include/dynamic_graph_bridge/ros_python_interpreter_server.hpp b/ros2/include/dynamic_graph_bridge/ros_python_interpreter_server.hpp
index 378665b..c9ba3dd 100644
--- a/ros2/include/dynamic_graph_bridge/ros_python_interpreter_server.hpp
+++ b/ros2/include/dynamic_graph_bridge/ros_python_interpreter_server.hpp
@@ -33,9 +33,9 @@ public:
      *
      * The first argument of "runCommandCallback"
      * (const std::string & command) is bound to
-     * (dynamic_graph_manager::srv::RunPythonCommand::Request).
+     * (dynamic_graph_bridge::srv::RunPythonCommand::Request).
      * And the second argument (std::string &result) is bound to
-     * (dynamic_graph_manager::srv::RunPythonCommand::Response)
+     * (dynamic_graph_bridge::srv::RunPythonCommand::Response)
      */
     typedef std::function<void(RunPythonCommandRequestPtr,
                                RunPythonCommandResponsePtr)>
@@ -47,9 +47,9 @@ public:
      *
      * The first argument of "runPythonFileCallback"
      * (std::string ifilename) is bound to
-     * (dynamic_graph_manager::srv::RunPythonCommand::Request).
+     * (dynamic_graph_bridge::srv::RunPythonCommand::Request).
      * And a fake second argument is simulated in
-     * (dynamic_graph_manager::srv::RunPythonCommand::Response)
+     * (dynamic_graph_bridge::srv::RunPythonCommand::Response)
      */
     typedef std::function<void(RunPythonFileRequestPtr,
                                RunPythonFileResponsePtr)>
@@ -86,7 +86,7 @@ public:
      * @param ifilename is the path to the script to execute
      */
     void run_python_file(const std::string ifilename,
-                         std::string& standarderror);
+                         std::string& result);
 
     /**
      * @brief start_ros_service advertize the "run_python_command" and
diff --git a/ros2/include/dynamic_graph_bridge/ros_sot_loader.hpp b/ros2/include/dynamic_graph_bridge/ros_sot_loader.hpp
deleted file mode 100644
index e69de29..0000000
diff --git a/ros2/include/dynamic_graph_bridge/sot_loader.hh b/ros2/include/dynamic_graph_bridge/sot_loader.hh
new file mode 100644
index 0000000..5b4b320
--- /dev/null
+++ b/ros2/include/dynamic_graph_bridge/sot_loader.hh
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2016,
+ * Olivier Stasse,
+ *
+ * CNRS
+ *
+ */
+/* -------------------------------------------------------------------------- */
+/* --- INCLUDES ------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+
+#ifndef _SOT_LOADER_HH_
+#define _SOT_LOADER_HH_
+
+// System includes
+#include <cassert>
+
+// STL includes
+#include <map>
+
+// Pinocchio includes
+#include <pinocchio/fwd.hpp>
+
+// Boost includes
+#include <boost/program_options.hpp>
+#include <boost/foreach.hpp>
+#include <boost/format.hpp>
+#include <thread>
+
+// ROS includes
+//#include "ros/ros.h"
+#include "std_srvs/srv/empty.hpp"
+#include <sensor_msgs/msg/joint_state.hpp>
+#include <tf2_ros/transform_broadcaster.h>
+
+// Sot Framework includes
+#include <sot/core/debug.hh>
+#include <sot/core/abstract-sot-external-interface.hh>
+
+// Dynamic-graph-bridge includes.
+#include <dynamic_graph_bridge/sot_loader_basic.hh>
+
+namespace po = boost::program_options;
+namespace dgs = dynamicgraph::sot;
+
+class SotLoader : public SotLoaderBasic {
+ protected:
+  /// Map of sensor readings
+  std::map<std::string, dgs::SensorValues> sensorsIn_;
+  /// Map of control values
+  std::map<std::string, dgs::ControlValues> controlValues_;
+
+  /// Angular values read by encoders
+  std::vector<double> angleEncoder_;
+  /// Angular values sent to motors
+  std::vector<double> control_;
+  /// Forces read by force sensors
+  std::vector<double> forces_;
+  /// Torques
+  std::vector<double> torques_;
+  /// Attitude of the robot computed by extended Kalman filter.
+  std::vector<double> baseAtt_;
+  /// Accelerations read by Accelerometers
+  std::vector<double> accelerometer_;
+  /// Angular velocity read by gyrometers
+  std::vector<double> gyrometer_;
+
+  /// URDF string description of the robot.
+  std::string robot_desc_string_;
+
+  /// The thread running dynamic graph
+  std::shared_ptr<std::thread> thread_;
+
+  // \brief Start control loop
+  virtual void startControlLoop();
+
+  // Robot Pose Publisher
+  std::shared_ptr<tf2_ros::TransformBroadcaster> freeFlyerPublisher_;
+
+  // Free flyer pose
+  geometry_msgs::msg::TransformStamped freeFlyerPose_;
+
+ public:
+  SotLoader();
+  virtual ~SotLoader();
+
+  // \brief Create a thread for ROS and start the control loop.
+  void initializeServices();
+
+  // \brief Compute one iteration of control.
+  // Basically calls fillSensors, the SoT and the readControl.
+  void oneIteration();
+
+  // \brief Fill the sensors value for the SoT.
+  void fillSensors(std::map<std::string, dgs::SensorValues> &sensorsIn);
+
+  // \brief Read the control computed by the SoT framework.
+  void readControl(std::map<std::string, dgs::ControlValues> &controlValues);
+
+  // \brief Prepare the SoT framework.
+  void setup();
+
+  // \brief Method for the thread implementing the starting and stopping part of dynamic_graph
+  void workThreadLoader();
+  
+  // \brief Join the thread.
+  void lthread_join();
+  typedef std::shared_ptr<SotLoader> SharedPtr;
+};
+
+#endif /* SOT_LOADER_HH_ */
diff --git a/ros2/include/dynamic_graph_bridge/sot_loader_basic.hh b/ros2/include/dynamic_graph_bridge/sot_loader_basic.hh
new file mode 100644
index 0000000..fd38405
--- /dev/null
+++ b/ros2/include/dynamic_graph_bridge/sot_loader_basic.hh
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2016,
+ * Olivier Stasse,
+ *
+ * CNRS
+ *
+ */
+/* -------------------------------------------------------------------------- */
+/* --- INCLUDES ------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+
+#ifndef _SOT_LOADER_BASIC_HH_
+#define _SOT_LOADER_BASIC_HH_
+
+// System includes
+#include <cassert>
+
+// STL includes
+#include <map>
+
+// Pinocchio includes
+#include <pinocchio/fwd.hpp>
+
+// Boost includes
+#include <boost/program_options.hpp>
+#include <boost/foreach.hpp>
+#include <boost/format.hpp>
+
+// ROS includes
+#include <rclcpp/rclcpp.hpp>
+#include "std_srvs/srv/empty.hpp"
+#include <sensor_msgs/msg/joint_state.hpp>
+
+// Sot Framework includes
+#include <sot/core/debug.hh>
+#include <sot/core/abstract-sot-external-interface.hh>
+#include "dynamic_graph_bridge/ros.hpp"
+
+namespace po = boost::program_options;
+namespace dgs = dynamicgraph::sot;
+
+class SotLoaderBasic {
+ protected:
+  // Dynamic graph is stopped.
+  bool dynamic_graph_stopped_;
+
+  /// \brief the sot-hrp2 controller
+  dgs::AbstractSotExternalInterface* sotController_;
+
+  po::variables_map vm_;
+  std::string dynamicLibraryName_;
+
+  /// \brief Handle on the SoT library.
+  void* sotRobotControllerLibrary_;
+
+  /// \brief Map between SoT state vector and some joint_state_links
+  //XmlRpc::XmlRpcValue stateVectorMap_;
+
+  /// \brief List of parallel joints from the state vector.
+  typedef std::vector<int> parallel_joints_to_state_vector_t;
+  parallel_joints_to_state_vector_t parallel_joints_to_state_vector_;
+
+  /// \brief Coefficient between parallel joints and the state vector.
+  std::vector<double> coefficient_parallel_joints_;
+  /// Advertises start_dynamic_graph services
+  rclcpp::Service<std_srvs::srv::Empty>::SharedPtr service_start_;
+
+  /// Advertises stop_dynamic_graph services
+  rclcpp::Service<std_srvs::srv::Empty>::SharedPtr service_stop_;
+
+  // Joint state publication.
+  rclcpp::Publisher<sensor_msgs::msg::JointState>::SharedPtr joint_pub_;
+
+  // Node reference
+  rclcpp::Node::SharedPtr nh_;
+  
+  // Joint state to be published.
+  sensor_msgs::msg::JointState joint_state_;
+
+  // Number of DOFs according to KDL.
+  int nbOfJoints_;
+  parallel_joints_to_state_vector_t::size_type nbOfParallelJoints_;
+
+  // Ordered list of joint names describing the robot state.
+  std::vector<std::string> stateVectorMap_;
+
+
+ public:
+  SotLoaderBasic();
+  virtual ~SotLoaderBasic();
+
+  // \brief Read user input to extract the path of the SoT dynamic library.
+  int parseOptions(int argc, char* argv[]);
+
+  /// \brief Load the SoT device corresponding to the robot.
+  void loadController();
+
+  // Initialize ROS Context
+  void initialize();
+
+  // Returns nodeHandle
+  rclcpp::Node::SharedPtr returnsNodeHandle();
+  
+  /// \brief Unload the library which handles the robot device.
+  void CleanUp();
+
+  // \brief Create ROS services start_dg and stop_dg.
+  virtual void initializeServices();
+
+  // \brief Callback function when starting dynamic graph.
+  void start_dg(const std::shared_ptr<std_srvs::srv::Empty::Request> request,
+                std::shared_ptr<std_srvs::srv::Empty::Response> response);
+
+  // \brief Callback function when stopping dynamic graph.
+  void stop_dg(const std::shared_ptr<std_srvs::srv::Empty::Request> request,
+               std::shared_ptr<std_srvs::srv::Empty::Response> response);
+
+  // \brief Read the state vector description based upon the robot links.
+  int readSotVectorStateParam();
+
+  // \brief Init publication of joint states.
+  int initPublication();
+
+  // \brief Get Status of dg.
+  bool isDynamicGraphStopped() { return dynamic_graph_stopped_; }
+
+  // \brief Specify the name of the dynamic library.
+  void setDynamicLibraryName(std::string& afilename);
+};
+
+#endif /* _SOT_LOADER_BASIC_HH_ */
diff --git a/ros2/scripts/remote_python_client.py b/ros2/scripts/remote_python_client.py
index d756d5f..9732f36 100644
--- a/ros2/scripts/remote_python_client.py
+++ b/ros2/scripts/remote_python_client.py
@@ -1,6 +1,6 @@
 #!/usr/bin/env python
 
-"""@package dynamic_graph_manager
+"""@package dynamic_graph_bridge
 
 @file
 @license License BSD-3-Clause
@@ -23,8 +23,8 @@ import signal
 import rclpy
 
 # Used to connect to ROS services
-from dynamic_graph_manager.ros.dgcompleter import DGCompleter
-from dynamic_graph_manager_cpp_bindings import RosPythonInterpreterClient
+from dynamic_graph_bridge.ros.dgcompleter import DGCompleter
+from dynamic_graph_bridge_cpp_bindings import RosPythonInterpreterClient
 
 
 def signal_handler(sig, frame):
diff --git a/ros2/scripts/run_command b/ros2/scripts/run_command
new file mode 120000
index 0000000..9feef56
--- /dev/null
+++ b/ros2/scripts/run_command
@@ -0,0 +1 @@
+remote_python_client.py
\ No newline at end of file
diff --git a/ros2/src/CMakeLists.txt b/ros2/src/CMakeLists.txt
index 93229f7..73d141e 100644
--- a/ros2/src/CMakeLists.txt
+++ b/ros2/src/CMakeLists.txt
@@ -3,6 +3,121 @@
 # Author: Maxmilien Naveau
 #
 
+
+#.rst:
+# .. command:: CUSTOM_DYNAMIC_GRAPH_PYTHON_MODULE ( SUBMODULENAME LIBRARYNAME TARGETNAME INSTALL_INIT_PY=1 SOURCE_PYTHON_MODULE=cmake/dynamic_graph/python-module-py.cc)
+#
+#   Add a python submodule to dynamic_graph
+#
+#   :param SUBMODULENAME: the name of the submodule (can be foo/bar),
+#
+#   :param LIBRARYNAME:   library to link the submodule with.
+#
+#   :param TARGETNAME:     name of the target: should be different for several
+#                   calls to the macro.
+#
+#   :param INSTALL_INIT_PY: if set to 1 install and generated a __init__.py file.
+#                   Set to 1 by default.
+#
+#   :param SOURCE_PYTHON_MODULE: Location of the cpp file for the python module in the package.
+#                   Set to cmake/dynamic_graph/python-module-py.cc by default.
+#
+#  .. note::
+#    Before calling this macro, set variable NEW_ENTITY_CLASS as
+#    the list of new Entity types that you want to be bound.
+#    Entity class name should match the name referencing the type
+#    in the factory.
+#
+MACRO(CUSTOM_DYNAMIC_GRAPH_PYTHON_MODULE SUBMODULENAME LIBRARYNAME TARGETNAME)
+
+  set(options DONT_INSTALL_INIT_PY)
+  set(oneValueArgs SOURCE_PYTHON_MODULE MODULE_HEADER)
+  cmake_parse_arguments(ARG "${options}" "${oneValueArgs}"
+                        "${multiValueArgs}" ${ARGN} )
+
+  # By default the __init__.py file is installed.
+  if(NOT DEFINED ARG_SOURCE_PYTHON_MODULE)
+    set(DYNAMICGRAPH_MODULE_HEADER ${ARG_MODULE_HEADER})
+    configure_file(
+      ${PROJECT_JRL_CMAKE_MODULE_DIR}/dynamic_graph/python-module-py.cc.in
+      ${PROJECT_BINARY_DIR}/ros2/src/dynamic_graph/${SUBMODULENAME}/python-module-py.cc
+      @ONLY
+      )
+    SET(ARG_SOURCE_PYTHON_MODULE "${PROJECT_BINARY_DIR}/ros2/src/dynamic_graph/${SUBMODULENAME}/python-module-py.cc")
+  endif()
+
+  IF(NOT DEFINED PYTHONLIBS_FOUND)
+    FINDPYTHON()
+  ELSEIF(NOT ${PYTHONLIBS_FOUND} STREQUAL "TRUE")
+    MESSAGE(FATAL_ERROR "Python has not been found.")
+  ENDIF()
+  if(NOT DEFINED Boost_PYTHON_LIBRARIES)
+    MESSAGE(FATAL_ERROR "Boost Python library must have been found to call this macro.")
+  endif()
+
+  SET(PYTHON_MODULE ${TARGETNAME})
+
+  ADD_LIBRARY(${PYTHON_MODULE}
+    MODULE
+    ${ARG_SOURCE_PYTHON_MODULE})
+
+  FILE(MAKE_DIRECTORY ${PROJECT_BINARY_DIR}/ros2/src/dynamic_graph/${SUBMODULENAME})
+
+  SET(PYTHON_INSTALL_DIR "${PYTHON_SITELIB}/dynamic_graph/${SUBMODULENAME}")
+  STRING(REGEX REPLACE "[^/]+" ".." PYTHON_INSTALL_DIR_REVERSE ${PYTHON_INSTALL_DIR})
+
+  SET_TARGET_PROPERTIES(${PYTHON_MODULE}
+    PROPERTIES PREFIX ""
+    OUTPUT_NAME dynamic_graph/${SUBMODULENAME}/wrap
+    BUILD_RPATH "${DYNAMIC_GRAPH_PLUGINDIR}:\$ORIGIN/${PYTHON_INSTALL_DIR_REVERSE}/${DYNAMIC_GRAPH_PLUGINDIR}"
+   )
+
+  IF (UNIX AND NOT APPLE)
+    TARGET_LINK_LIBRARIES(${PYTHON_MODULE} ${PUBLIC_KEYWORD} "-Wl,--no-as-needed")
+  ENDIF(UNIX AND NOT APPLE)
+  TARGET_LINK_LIBRARIES(${PYTHON_MODULE} ${PUBLIC_KEYWORD} ${LIBRARYNAME} ${PYTHON_LIBRARY} dynamic-graph::dynamic-graph)
+  TARGET_LINK_BOOST_PYTHON(${PYTHON_MODULE} ${PUBLIC_KEYWORD})
+  if(PROJECT_NAME STREQUAL "dynamic-graph-python")
+    TARGET_LINK_LIBRARIES(${PYTHON_MODULE} ${PUBLIC_KEYWORD} dynamic-graph-python)
+  else()
+    TARGET_LINK_LIBRARIES(${PYTHON_MODULE} ${PUBLIC_KEYWORD} dynamic-graph-python::dynamic-graph-python)
+  endif()
+
+  TARGET_INCLUDE_DIRECTORIES(${PYTHON_MODULE} SYSTEM PRIVATE ${PYTHON_INCLUDE_DIRS})
+
+  #
+  # Installation
+  #
+
+  INSTALL(TARGETS ${PYTHON_MODULE}
+    DESTINATION
+    ${PYTHON_INSTALL_DIR})
+
+  SET(ENTITY_CLASS_LIST "")
+  FOREACH (ENTITY ${NEW_ENTITY_CLASS})
+    SET(ENTITY_CLASS_LIST "${ENTITY_CLASS_LIST}${ENTITY}('')\n")
+  ENDFOREACH(ENTITY ${NEW_ENTITY_CLASS})
+
+  # Install if not DONT_INSTALL_INIT_PY
+  if(NOT DONT_INSTALL_INIT_PY)
+
+    CONFIGURE_FILE(
+      ${PROJECT_JRL_CMAKE_MODULE_DIR}/dynamic_graph/submodule/__init__.py.cmake
+      ${PROJECT_BINARY_DIR}/ros2/src/dynamic_graph/${SUBMODULENAME}/__init__.py
+      )
+
+    INSTALL(
+      FILES ${PROJECT_BINARY_DIR}/ros2/src/dynamic_graph/${SUBMODULENAME}/__init__.py
+      DESTINATION ${PYTHON_INSTALL_DIR}
+      )
+
+  endif()
+
+ENDMACRO(CUSTOM_DYNAMIC_GRAPH_PYTHON_MODULE SUBMODULENAME)
+
+
+
+
 # Main Library
 set(${PROJECT_NAME}_HEADERS
     ${PROJECT_SOURCE_DIR}/ros2/include/${PROJECT_NAME}/ros_parameter.hpp
@@ -51,38 +166,44 @@ endif(NOT INSTALL_PYTHON_INTERFACE_ONLY)
 # Dynamic graph ros plugin.
 set(plugins ros_publish ros_subscribe)
 foreach(plugin ${plugins})
-  get_filename_component(LIBRARY_NAME ${plugin} NAME)
-  add_library(${LIBRARY_NAME} SHARED
+  get_filename_component(plugin_library_name ${plugin} NAME)
+  add_library(${plugin_library_name} SHARED
     ${PROJECT_SOURCE_DIR}/ros2/src/${plugin}.cpp #
     ${PROJECT_SOURCE_DIR}/ros2/src/${plugin}.hpp #
   )
   if(SUFFIX_SO_VERSION)
-    set_target_properties(${LIBRARY_NAME} PROPERTIES SOVERSION
+    set_target_properties(${plugin_library_name} PROPERTIES SOVERSION
                                                      ${PROJECT_VERSION})
   endif(SUFFIX_SO_VERSION)
   target_link_libraries(
-    ${LIBRARY_NAME} ${${LIBRARY_NAME}_deps} ${catkin_LIBRARIES} ros_bridge
+    ${plugin_library_name} ${${plugin_library_name}_deps} ${catkin_LIBRARIES} ros_bridge
     dynamic_graph_bridge_msgs::dynamic_graph_bridge_msgs__rosidl_typesupport_cpp
   )
+  target_include_directories(${plugin_library_name}
+    PUBLIC $<INSTALL_INTERFACE:include>
+    PRIVATE ${PROJECT_SOURCE_DIR}/ros2/include)
   if(NOT INSTALL_PYTHON_INTERFACE_ONLY)
     install(
-      TARGETS ${LIBRARY_NAME}
+      TARGETS ${plugin_library_name}
       EXPORT ${TARGETS_EXPORT_NAME}
       DESTINATION ${DYNAMIC_GRAPH_PLUGINDIR})
   endif(NOT INSTALL_PYTHON_INTERFACE_ONLY)
   if(BUILD_PYTHON_INTERFACE)
-    string(REPLACE - _ PYTHON_LIBRARY_NAME ${LIBRARY_NAME})
+    string(REPLACE - _ PYTHON_LIBRARY_NAME ${plugin_library_name})
     if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/${plugin}-python-module-py.cc")
-      dynamic_graph_python_module(
-        "ros/${PYTHON_LIBRARY_NAME}" ${LIBRARY_NAME}
+      custom_dynamic_graph_python_module(
+        "ros/${PYTHON_LIBRARY_NAME}" ${plugin_library_name}
         ${PROJECT_NAME}-${PYTHON_LIBRARY_NAME}-wrap SOURCE_PYTHON_MODULE
         "${CMAKE_CURRENT_SOURCE_DIR}/${plugin}-python-module-py.cc")
     elseif(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/${plugin}-python.hh")
-      dynamic_graph_python_module(
-        "ros/${PYTHON_LIBRARY_NAME}" ${LIBRARY_NAME}
+      custom_dynamic_graph_python_module(
+        "ros/${PYTHON_LIBRARY_NAME}" ${plugin_library_name}
         ${PROJECT_NAME}-${PYTHON_LIBRARY_NAME}-wrap MODULE_HEADER
         "${CMAKE_CURRENT_SOURCE_DIR}/${plugin}-python.hh")
     endif()
+    target_include_directories(${PROJECT_NAME}-${PYTHON_LIBRARY_NAME}-wrap
+      PUBLIC $<INSTALL_INTERFACE:include>
+      PRIVATE ${PROJECT_SOURCE_DIR}/ros2/include)
   endif(BUILD_PYTHON_INTERFACE)
 endforeach(plugin)
 
@@ -92,23 +213,26 @@ if(BUILD_PYTHON_INTERFACE)
   python_install_on_site("dynamic_graph/ros" "dgcompleter.py")
 endif(BUILD_PYTHON_INTERFACE)
 
-# Stand alone embedded intepreter with a robot controller.
-# add_executable(geometric_simu 
-#   geometric_simu.cpp
-#   sot_loader.cpp  
-#   sot_loader_basic.cpp)
-# target_link_libraries(
-#   geometric_simu Boost::program_options ${CMAKE_DL_LIBS} ${catkin_LIBRARIES}
-#   ros_bridge
-#   dynamic_graph_bridge_msgs::dynamic_graph_bridge_msgs__rosidl_typesupport_cpp)
-# install(TARGETS geometric_simu DESTINATION lib/${PROJECT_NAME})
-
 # Sot loader library
-# add_library(sot_loader sot_loader.cpp sot_loader_basic.cpp)
-# target_link_libraries(
-#   sot_loader Boost::program_options ${catkin_LIBRARIES} ros_bridge
-#   dynamic_graph_bridge_msgs::dynamic_graph_bridge_msgs__rosidl_typesupport_cpp)
-# install(
-#   TARGETS sot_loader
-#   EXPORT ${TARGETS_EXPORT_NAME}
-#   DESTINATION lib)
+add_library(sot_loader sot_loader.cpp sot_loader_basic.cpp)
+ target_link_libraries(
+  sot_loader Boost::program_options ${catkin_LIBRARIES} ros_bridge
+  dynamic_graph_bridge_msgs::dynamic_graph_bridge_msgs__rosidl_typesupport_cpp)
+target_include_directories(sot_loader
+  PUBLIC $<INSTALL_INTERFACE:include>
+  PRIVATE ${PROJECT_SOURCE_DIR}/ros2/include)
+install(
+  TARGETS sot_loader
+  EXPORT ${TARGETS_EXPORT_NAME}
+  DESTINATION lib)
+
+# Stand alone embedded intepreter with a robot controller.
+add_executable(geometric_simu programs/geometric_simu.cpp)
+target_link_libraries(
+  geometric_simu Boost::program_options ${CMAKE_DL_LIBS} ${catkin_LIBRARIES}
+  ros_bridge sot_loader
+  dynamic_graph_bridge_msgs::dynamic_graph_bridge_msgs__rosidl_typesupport_cpp)
+target_include_directories(geometric_simu
+  PUBLIC $<INSTALL_INTERFACE:include>
+  PRIVATE ${PROJECT_SOURCE_DIR}/ros2/include)
+install(TARGETS geometric_simu DESTINATION lib/${PROJECT_NAME})
diff --git a/ros2/src/dynamic_graph/ros/__init__.py b/ros2/src/dynamic_graph/ros/__init__.py
index cc585ac..12ef9f7 100644
--- a/ros2/src/dynamic_graph/ros/__init__.py
+++ b/ros2/src/dynamic_graph/ros/__init__.py
@@ -1,4 +1,4 @@
-"""@package dynamic_graph_manager
+"""@package dynamic_graph_bridge
 
 @file __init__.py
 @author Maximilien Naveau (maximilien.naveau@gmail.com)
diff --git a/ros2/src/dynamic_graph/ros/dgcompleter.py b/ros2/src/dynamic_graph/ros/dgcompleter.py
index aa24433..7c40708 100644
--- a/ros2/src/dynamic_graph/ros/dgcompleter.py
+++ b/ros2/src/dynamic_graph/ros/dgcompleter.py
@@ -36,7 +36,7 @@ its input.
 """
 
 import ast
-from dynamic_graph_manager_cpp_bindings import RosPythonInterpreterClient
+from dynamic_graph_bridge_cpp_bindings import RosPythonInterpreterClient
 
 __all__ = ["DGCompleter"]
 
@@ -84,7 +84,7 @@ class DGCompleter:
         # self.client.run_python_command("global local_completer")
         cmd = 'local_completer.complete("' + text + '",' + str(state) + ")"
         response = self.client.run_python_command(cmd)
-        if not response or response is "\n" or response is None:
+        if not response or response == "\n" or response is None:
             response = None
             self.buffer = []
         else:
diff --git a/ros2/src/dynamic_graph/ros/ros.py b/ros2/src/dynamic_graph/ros/ros.py
index 2bb925d..ca9c495 100644
--- a/ros2/src/dynamic_graph/ros/ros.py
+++ b/ros2/src/dynamic_graph/ros/ros.py
@@ -1,4 +1,4 @@
-"""@package dynamic_graph_manager
+"""@package dynamic_graph_bridge
 
 @file ros.py
 @author Maximilien Naveau (maximilien.naveau@gmail.com)
@@ -12,8 +12,8 @@ between ROS and the dynamic graph
 """
 
 from dynamic_graph import plug
-from dynamic_graph_manager.dynamic_graph.ros_entities import RosPublish
-from dynamic_graph_manager.dynamic_graph.ros_entities import RosSubscribe
+from dynamic_graph_bridge.dynamic_graph.ros_entities import RosPublish
+from dynamic_graph_bridge.dynamic_graph.ros_entities import RosSubscribe
 
 
 class Ros(object):
diff --git a/ros2/src/programs/geometric_simu.cpp b/ros2/src/programs/geometric_simu.cpp
index 8dbe445..0781323 100644
--- a/ros2/src/programs/geometric_simu.cpp
+++ b/ros2/src/programs/geometric_simu.cpp
@@ -12,7 +12,7 @@
 #include <dynamic-graph/real-time-logger.h>
 
 #include <dynamic_graph_bridge/sot_loader.hh>
-#include <dynamic_graph_bridge/ros2_init.hh>
+#include <dynamic_graph_bridge/ros.hpp>
 
 int main(int argc, char *argv[]) {
   ::dynamicgraph::RealTimeLogger::instance()
@@ -20,23 +20,18 @@ int main(int argc, char *argv[]) {
 
   rclcpp::init(argc, argv);
 
-  dynamicgraph::RosContext::SharedPtr aRosContext =
-    std::make_shared<dynamicgraph::RosContext>();
-
-  aRosContext->rosInit();
-
   SotLoader aSotLoader;
   if (aSotLoader.parseOptions(argc, argv) < 0) return -1;
 
   // Advertize service "(start|stop)_dynamic_graph" and
   // load parameter "robot_description in SoT.
-  aSotLoader.initializeFromRosContext(aRosContext);
+  aSotLoader.initialize();
 
   // Load dynamic library and run python prologue.
   aSotLoader.initPublication();
 
   aSotLoader.initializeServices();
 
-  aRosContext->mtExecutor->spin();
+  dynamic_graph_bridge::ros_spin();
   return 0;
 }
diff --git a/ros2/src/ros.cpp b/ros2/src/ros.cpp
index b765654..4917f4d 100644
--- a/ros2/src/ros.cpp
+++ b/ros2/src/ros.cpp
@@ -269,7 +269,9 @@ RosNodePtr get_ros_node(std::string node_name)
     {
         /** RosNode instanciation */
         GLOBAL_LIST_OF_ROS_NODE[node_name] =
-            std::make_shared<RosNode>(node_name, "dynamic_graph_manager");
+            std::make_shared<RosNode>(
+                node_name, "dynamic_graph_bridge");
+        
     }
     /** Return a reference to the node handle so any function can use it */
     return GLOBAL_LIST_OF_ROS_NODE[node_name];
@@ -311,7 +313,7 @@ void ros_clean()
 
 bool ros_ok()
 {
-    return rclcpp::ok() && rclcpp::is_initialized();
+    return rclcpp::ok();
 }
 
 void ros_spin()
diff --git a/ros2/src/ros_publish-python-module-py.cc b/ros2/src/ros_publish-python-module-py.cc
index 5134a78..831462b 100644
--- a/ros2/src/ros_publish-python-module-py.cc
+++ b/ros2/src/ros_publish-python-module-py.cc
@@ -1,19 +1,21 @@
 #include <dynamic-graph/python/module.hh>
-#include "ros_publish.hh"
+#include "ros_publish.hpp"
 
+namespace dgb = dynamic_graph_bridge;
 namespace dg = dynamicgraph;
 
+
 BOOST_PYTHON_MODULE(wrap)
 {
   bp::import("dynamic_graph");
 
-  dg::python::exposeEntity<dg::RosPublish, bp::bases<dg::Entity>,
+  dg::python::exposeEntity<dgb::RosPublish, bp::bases<dg::Entity>,
                            dg::python::AddCommands>()
-      .def("clear", &dg::RosPublish::clear,
+      .def("clear", &dgb::RosPublish::clear,
            "Remove all signals writing data to a ROS topic")
-      .def("rm", &dg::RosPublish::rm,
+      .def("rm", &dgb::RosPublish::rm,
            "Remove a signal writing data to a ROS topic",
            bp::args("signal_name"))
-      .def("list", &dg::RosPublish::list,
+      .def("list", &dgb::RosPublish::list,
            "List signals writing data to a ROS topic");
 }
diff --git a/ros2/src/ros_publish.cpp b/ros2/src/ros_publish.cpp
index 8bbd459..9ed9b7f 100644
--- a/ros2/src/ros_publish.cpp
+++ b/ros2/src/ros_publish.cpp
@@ -7,9 +7,8 @@
  * @brief Implements the RosPublish class.
  */
 
-#include "dynamic_graph_bridge/ros_entities/ros_publish.hpp"
-
 #include "dynamic_graph_bridge/ros.hpp"
+#include "ros_publish.hpp"
 
 namespace dynamic_graph_bridge
 {
diff --git a/ros2/src/ros_publish.hpp b/ros2/src/ros_publish.hpp
index edb4f6c..68b89ef 100644
--- a/ros2/src/ros_publish.hpp
+++ b/ros2/src/ros_publish.hpp
@@ -18,7 +18,7 @@
 #include <map>
 #include <mutex>
 
-#include "dynamic_graph_bridge/ros_entities/dg_ros_mapping.hpp"
+#include "dg_ros_mapping.hpp"
 
 namespace dynamic_graph_bridge
 {
diff --git a/ros2/src/ros_publish.hxx b/ros2/src/ros_publish.hxx
index 8e1422c..9882cbe 100644
--- a/ros2/src/ros_publish.hxx
+++ b/ros2/src/ros_publish.hxx
@@ -9,7 +9,7 @@
 
 #pragma once
 
-#include "dynamic_graph_bridge/ros_entities/dg_ros_mapping.hpp"
+#include "dg_ros_mapping.hpp"
 
 namespace dynamic_graph_bridge
 {
diff --git a/ros2/src/ros_python_interpreter_client.cpp b/ros2/src/ros_python_interpreter_client.cpp
index 4870016..dd73256 100644
--- a/ros2/src/ros_python_interpreter_client.cpp
+++ b/ros2/src/ros_python_interpreter_client.cpp
@@ -22,13 +22,13 @@ RosPythonInterpreterClient::RosPythonInterpreterClient()
 
     // Create a client for the single python command service of the
     // DynamicGraphManager.
-    run_command_service_name_ = "/dynamic_graph_manager/run_python_command";
+    run_command_service_name_ = "/dynamic_graph_bridge/run_python_command";
     run_command_request_ = std::make_shared<RunPythonCommandSrvType::Request>();
     connect_to_rosservice_run_python_command();
 
     // Create a client for the python file reading service of the
     // DynamicGraphManager.
-    run_script_service_name_ = "/dynamic_graph_manager/run_python_file";
+    run_script_service_name_ = "/dynamic_graph_bridge/run_python_file";
     run_file_request_ = std::make_shared<RunPythonFileSrvType::Request>();
     connect_to_rosservice_run_python_script();
     timeout_connection_s_ = DurationSec(1);
diff --git a/ros2/src/ros_sot_loader.cpp b/ros2/src/ros_sot_loader.cpp
deleted file mode 100644
index e69de29..0000000
diff --git a/ros2/src/ros_subscribe-python-module-py.cc b/ros2/src/ros_subscribe-python-module-py.cc
index dd02bf5..338cf5a 100644
--- a/ros2/src/ros_subscribe-python-module-py.cc
+++ b/ros2/src/ros_subscribe-python-module-py.cc
@@ -1,19 +1,20 @@
 #include <dynamic-graph/python/module.hh>
-#include "ros_subscribe.hh"
+#include "ros_subscribe.hpp"
 
+namespace dgb = dynamic_graph_bridge;
 namespace dg = dynamicgraph;
 
 BOOST_PYTHON_MODULE(wrap)
 {
   bp::import("dynamic_graph");
 
-  dg::python::exposeEntity<dg::RosSubscribe, bp::bases<dg::Entity>,
+  dg::python::exposeEntity<dgb::RosSubscribe, bp::bases<dg::Entity>,
                            dg::python::AddCommands>()
-      .def("clear", &dg::RosSubscribe::clear,
+      .def("clear", &dgb::RosSubscribe::clear,
            "Remove all signals reading data from a ROS topic")
-      .def("rm", &dg::RosSubscribe::rm,
+      .def("rm", &dgb::RosSubscribe::rm,
            "Remove a signal reading data from a ROS topic",
            bp::args("signal_name"))
-      .def("list", &dg::RosSubscribe::list,
+      .def("list", &dgb::RosSubscribe::list,
            "List signals reading data from a ROS topic");
 }
diff --git a/ros2/src/ros_subscribe.cpp b/ros2/src/ros_subscribe.cpp
index bf02200..9d65b3d 100644
--- a/ros2/src/ros_subscribe.cpp
+++ b/ros2/src/ros_subscribe.cpp
@@ -7,11 +7,8 @@
  * @date 2019-05-22
  */
 
-#include "dynamic_graph_bridge/ros_entities/ros_subscribe.hpp"
-
 #include <dynamic-graph/factory.h>
-
-#include "dynamic_graph_bridge/dynamic_graph_manager.hpp"
+#include "ros_subscribe.hpp"
 
 namespace dynamic_graph_bridge
 {
diff --git a/ros2/src/ros_subscribe.hpp b/ros2/src/ros_subscribe.hpp
index a4e40c0..5f361be 100644
--- a/ros2/src/ros_subscribe.hpp
+++ b/ros2/src/ros_subscribe.hpp
@@ -18,7 +18,7 @@
 #include <map>
 #include <mutex>
 
-#include "dynamic_graph_bridge/ros_entities/dg_ros_mapping.hpp"
+#include "dg_ros_mapping.hpp"
 
 namespace dynamic_graph_bridge
 {
diff --git a/ros2/src/ros_subscribe.hxx b/ros2/src/ros_subscribe.hxx
index ccde69d..c3a4b41 100644
--- a/ros2/src/ros_subscribe.hxx
+++ b/ros2/src/ros_subscribe.hxx
@@ -11,7 +11,7 @@
 
 #include <message_filters/message_traits.h>
 
-#include "dynamic_graph_bridge/ros_entities/dg_ros_mapping.hpp"
+#include "dg_ros_mapping.hpp"
 
 namespace dynamic_graph_bridge
 {
diff --git a/ros2/src/sot_loader.cpp b/ros2/src/sot_loader.cpp
new file mode 100644
index 0000000..66511e4
--- /dev/null
+++ b/ros2/src/sot_loader.cpp
@@ -0,0 +1,281 @@
+/*
+ * Copyright 2011,
+ * Olivier Stasse,
+ *
+ * CNRS
+ *
+ */
+/* -------------------------------------------------------------------------- */
+/* --- INCLUDES ------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+
+#include <dynamic_graph_bridge/sot_loader.hh>
+#include "dynamic_graph_bridge/ros.hpp"
+#include <std_msgs/msg/string.hpp>
+
+// POSIX.1-2001
+#include <dlfcn.h>
+
+using namespace std;
+using namespace dynamicgraph::sot;
+namespace po = boost::program_options;
+
+struct DataToLog {
+  const std::size_t N;
+  std::size_t idx, iter;
+
+  std::vector<std::size_t> iters;
+  std::vector<double> times;
+  std::vector<double> ittimes;
+
+  DataToLog(std::size_t N_)
+    : N(N_)
+    , idx(0)
+    , iter(0)
+    , iters(N, 0)
+    , times(N, 0)
+    , ittimes(N, 0)
+  {}
+
+  void record(const double t, const double itt) {
+    iters  [idx] = iter;
+    times  [idx] = t;
+    ittimes[idx] = itt;
+    ++idx;
+    ++iter;
+    if (idx == N) idx = 0;
+  }
+
+  void save(const char *prefix) {
+    std::ostringstream oss;
+    oss << prefix << "-times.log";
+
+    std::ofstream aof(oss.str().c_str());
+    if (aof.is_open()) {
+      for (std::size_t k = 0; k < N; ++k) {
+        aof
+          << iters  [(idx + k) % N] << ' '
+          << times  [(idx + k) % N] << ' '
+          << ittimes[(idx + k) % N] << '\n';
+      }
+    }
+    aof.close();
+  }
+};
+
+
+SotLoader::SotLoader()
+    : SotLoaderBasic(),
+      sensorsIn_(),
+      controlValues_(),
+      angleEncoder_(),
+      control_(),
+      forces_(),
+      torques_(),
+      baseAtt_(),
+      accelerometer_(3),
+      gyrometer_(3) {
+}
+
+
+SotLoader::~SotLoader() {
+  dynamic_graph_stopped_ = true;
+  lthread_join();
+}
+
+void SotLoader::startControlLoop() {
+  thread_ = std::make_shared<std::thread>(&SotLoader::workThreadLoader,this);
+}
+
+void SotLoader::initializeServices() {
+  SotLoaderBasic::initializeServices();
+
+  freeFlyerPose_.header.frame_id = "odom";
+  freeFlyerPose_.child_frame_id = "base_link";
+
+  if (nh_==0) {
+    logic_error aLogicError("SotLoaderBasic::initializeFromRosContext aRosCtxt is empty !");
+    throw aLogicError;
+  }
+
+  if (not nh_->has_parameter("tf_base_link"))
+    nh_->declare_parameter("tf_base_link",std::string("base_link"));
+
+  if (nh_->get_parameter("tf_base_link",
+                      freeFlyerPose_.child_frame_id)) {
+    RCLCPP_INFO_STREAM(rclcpp::get_logger("dynamic_graph_bridge"),
+                       "Publishing " << freeFlyerPose_.child_frame_id <<
+                       " wrt " << freeFlyerPose_.header.frame_id);
+  }
+
+  // Temporary fix. TODO: where should nbOfJoints_ be initialized from?
+
+  angleEncoder_.resize(static_cast<std::size_t>(nbOfJoints_));
+  control_.resize(static_cast<std::size_t>(nbOfJoints_));
+
+  // Creates a publisher for the free flyer transform.
+  freeFlyerPublisher_ = std::make_shared<tf2_ros::TransformBroadcaster>
+    (nh_);
+}
+
+void SotLoader::fillSensors(map<string, dgs::SensorValues> &sensorsIn) {
+  // Update joint values.w
+  assert(control_.size() == angleEncoder_.size());
+
+  sensorsIn["joints"].setName("angle");
+  for (unsigned int i = 0; i < control_.size(); i++) angleEncoder_[i] = control_[i];
+  sensorsIn["joints"].setValues(angleEncoder_);
+}
+
+void SotLoader::readControl(map<string, dgs::ControlValues> &controlValues) {
+  // Update joint values.
+  std::map<std::string, dgs::ControlValues>::iterator it_ctrl_map;
+  it_ctrl_map = controlValues.find("control");
+  if (it_ctrl_map == controlValues.end())
+    {
+      invalid_argument anInvalidArgument("control is not present in controlValues !");
+      throw anInvalidArgument;
+    }
+  control_ = controlValues["control"].getValues();
+
+#ifdef NDEBUG
+  // Debug
+  std::map<std::string, dgs::ControlValues>::iterator it = controlValues.begin();
+  sotDEBUG(30) << "ControlValues to be broadcasted:" << std::endl;
+  for (; it != controlValues.end(); it++) {
+    sotDEBUG(30) << it->first << ":";
+    std::vector<double> ctrlValues_ = it->second.getValues();
+    std::vector<double>::iterator it_d = ctrlValues_.begin();
+    for (; it_d != ctrlValues_.end(); it_d++) sotDEBUG(30) << *it_d << " ";
+    sotDEBUG(30) << std::endl;
+  }
+  sotDEBUG(30) << "End ControlValues" << std::endl;
+#endif
+  // Check if the size if coherent with the robot description.
+  if (control_.size() != (unsigned int)nbOfJoints_) {
+    nbOfJoints_ = control_.size();
+    angleEncoder_.resize(nbOfJoints_);
+  }
+
+  // Publish the data.
+  std::vector<string> joint_state_names;
+  joint_state_.name = joint_state_names;
+  joint_state_.header.stamp = rclcpp::Clock().now();
+
+  if (joint_state_.position.size()!=nbOfJoints_+parallel_joints_to_state_vector_.size())
+    {
+      joint_state_.position.resize(nbOfJoints_+parallel_joints_to_state_vector_.size());
+      joint_state_.velocity.resize(nbOfJoints_+parallel_joints_to_state_vector_.size());
+      joint_state_.effort.resize(nbOfJoints_+parallel_joints_to_state_vector_.size());
+    }
+
+  for (int i = 0; i < nbOfJoints_; i++) {
+    joint_state_.position[i] = angleEncoder_[i];
+  }
+
+  std::cerr << "parallel_joints_to_state_vector_.size(): "
+            << parallel_joints_to_state_vector_.size()
+            << std::endl;
+
+  for (unsigned int i = 0; i < parallel_joints_to_state_vector_.size(); i++) {
+    joint_state_.position[i + nbOfJoints_] =
+        coefficient_parallel_joints_[i] * angleEncoder_[parallel_joints_to_state_vector_[i]];
+  }
+
+  joint_pub_->publish(joint_state_);
+
+  // Get the estimation of the robot base.
+  it_ctrl_map = controlValues.find("baseff");
+  if (it_ctrl_map == controlValues.end())
+    {
+      invalid_argument anInvalidArgument("baseff is not present in controlValues !");
+      throw anInvalidArgument;
+    }
+
+  std::cerr << "Reached poseValue_" << std::endl;
+  std::vector<double> poseValue = controlValues["baseff"].getValues();
+
+  freeFlyerPose_.transform.translation.x = poseValue[0];
+  freeFlyerPose_.transform.translation.y = poseValue[1];
+  freeFlyerPose_.transform.translation.z = poseValue[2];
+
+  freeFlyerPose_.transform.rotation.w = poseValue[3];
+  freeFlyerPose_.transform.rotation.x = poseValue[4];
+  freeFlyerPose_.transform.rotation.y = poseValue[5];
+  freeFlyerPose_.transform.rotation.z = poseValue[6];
+
+  freeFlyerPose_.header.stamp = joint_state_.header.stamp;
+  // Publish
+  freeFlyerPublisher_->sendTransform(freeFlyerPose_);
+  std::cerr << "end of readControl" << std::endl;
+}
+
+void SotLoader::setup() {
+  fillSensors(sensorsIn_);
+  sotController_->setupSetSensors(sensorsIn_);
+  sotController_->getControl(controlValues_);
+  readControl(controlValues_);
+}
+
+void SotLoader::oneIteration() {
+  fillSensors(sensorsIn_);
+  try {
+    sotController_->nominalSetSensors(sensorsIn_);
+    sotController_->getControl(controlValues_);
+  } catch (std::exception &) {
+    throw;
+  }
+
+  readControl(controlValues_);
+}
+
+void SotLoader::lthread_join()  {
+  if (thread_!=0)
+    if (thread_->joinable())
+      thread_->join();
+}
+
+void SotLoader::workThreadLoader() {
+  double periodd;
+
+  /// Declare parameters
+  if (not nh_->has_parameter("dt"))
+    nh_->declare_parameter<double>("dt",0.01);
+  if (not nh_->has_parameter("paused"))
+    nh_->declare_parameter<bool>("paused",false);
+
+  //
+  nh_->get_parameter_or("dt",periodd,0.001);
+  rclcpp::Rate rate(1/periodd); // 1 kHz
+
+  DataToLog dataToLog(5000);
+
+  rate.reset();
+  while (rclcpp::ok() && isDynamicGraphStopped()) {
+    rate.sleep();
+  }
+
+  bool paused;
+  rclcpp::Clock aClock;
+  rclcpp::Time timeOrigin = aClock.now();
+  rclcpp::Time time;
+  while (rclcpp::ok() && !isDynamicGraphStopped()) {
+
+    // Check if the user did not paused geometric_simu
+    nh_->get_parameter_or("paused", paused, false);
+
+    if (!paused) {
+      time = aClock.now();
+      oneIteration();
+
+      rclcpp::Duration d = aClock.now() - time;
+      dataToLog.record((time - timeOrigin).nanoseconds()*1.0e9, d.nanoseconds()*1.0e9);
+    }
+    rate.sleep();
+  }
+  dataToLog.save("/tmp/geometric_simu");
+  std::cerr << "End of this thread: "
+            << std::this_thread::get_id()
+            << std::endl;
+    
+}
diff --git a/ros2/src/sot_loader_basic.cpp b/ros2/src/sot_loader_basic.cpp
new file mode 100644
index 0000000..6941472
--- /dev/null
+++ b/ros2/src/sot_loader_basic.cpp
@@ -0,0 +1,249 @@
+/*
+ * Copyright 2016,
+ * Olivier Stasse,
+ *
+ * CNRS
+ *
+ */
+/* -------------------------------------------------------------------------- */
+/* --- INCLUDES ------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+
+#include "dynamic_graph_bridge/sot_loader_basic.hh"
+#include "dynamic_graph_bridge/ros_parameter.hpp"
+
+#include <dynamic-graph/pool.h>
+
+#include <exception>
+
+// POSIX.1-2001
+#include <dlfcn.h>
+
+using namespace std;
+using namespace dynamicgraph::sot;
+namespace po = boost::program_options;
+
+SotLoaderBasic::SotLoaderBasic() : dynamic_graph_stopped_(true), sotRobotControllerLibrary_(0) {
+  nh_ = dynamic_graph_bridge::get_ros_node("SotLoaderBasic");
+}
+
+SotLoaderBasic::~SotLoaderBasic()
+{
+  dynamic_graph_bridge::ros_shutdown("SotLoaderBasic");
+}
+
+void SotLoaderBasic::initialize()
+{
+}
+
+rclcpp::Node::SharedPtr SotLoaderBasic::returnsNodeHandle() {
+  return nh_;
+}
+int SotLoaderBasic::initPublication() {
+
+  // Prepare message to be published
+  joint_pub_ = nh_->create_publisher<sensor_msgs::msg::JointState>("joint_states", 1);
+
+  return 0;
+}
+
+void SotLoaderBasic::initializeServices() {
+  RCLCPP_INFO(rclcpp::get_logger("dynamic_graph_bridge"),
+               "Ready to start dynamic graph.");
+
+  using namespace std::placeholders;
+  service_start_ = nh_->create_service<std_srvs::srv::Empty>("start_dynamic_graph",
+                                                             std::bind(&SotLoaderBasic::start_dg,
+                                                                       this,std::placeholders::_1,
+                                                                       std::placeholders::_2));
+
+  service_stop_ = nh_->create_service<std_srvs::srv::Empty>("stop_dynamic_graph",
+                                                            std::bind(&SotLoaderBasic::stop_dg,
+                                                                      this,std::placeholders::_1,
+                                                                      std::placeholders::_2));
+
+  dynamicgraph::parameter_server_read_robot_description(nh_);
+}
+
+void SotLoaderBasic::setDynamicLibraryName(std::string& afilename) { dynamicLibraryName_ = afilename; }
+
+int SotLoaderBasic::readSotVectorStateParam() {
+  std::map<std::string, int> from_state_name_to_state_vector;
+  std::map<std::string, std::string> from_parallel_name_to_state_vector_name;
+
+  if (!nh_)    {
+      throw std::logic_error("SotLoaderBasic::readSotVectorStateParam() nh_ not initialized");
+  }
+
+  // It is necessary to declare parameters first
+  // before trying to access them.
+  if (not nh_->has_parameter("state_vector_map"))
+    nh_->declare_parameter("state_vector_map",std::vector<std::string>{});
+  if (not nh_->has_parameter("joint_state_parallel"))
+    nh_->declare_parameter("joint_state_parallel",std::vector<std::string>{});
+
+  // Read the state vector of the robot
+  // Defines the order in which the actuators are ordered
+  try {
+    std::string aParameterName("state_vector_map");
+    if (!nh_->get_parameter(aParameterName,stateVectorMap_)) {
+      logic_error aLogicError(
+        "SotLoaderBasic::readSotVectorStateParam : State_vector_map is empty");
+      throw aLogicError;
+    }
+    RCLCPP_INFO(nh_->get_logger(), "state_vector_map parameter size %d",
+                stateVectorMap_.size());
+  }
+  catch (exception &e)
+    {
+      std::throw_with_nested(std::logic_error(
+        "Unable to call nh_->get_parameter"));
+    }
+
+  nbOfJoints_ = static_cast<int>(stateVectorMap_.size());
+  nbOfParallelJoints_ = 0;
+
+  // Read the parallel joints.
+  // Specify the constraint between the joints
+  // Currently acts as a mimic or a an inverse mimic joint.
+
+  std::string prefix("joint_state_parallel");
+  std::map<std::string,rclcpp::Parameter> joint_state_parallel;
+  nh_->get_parameters(prefix,joint_state_parallel);
+
+  // Iterates over the map joint_state_parallel
+  for ( std::map<std::string,rclcpp::Parameter>::iterator
+          it_map_expression = joint_state_parallel.begin();
+        it_map_expression != joint_state_parallel.end();
+        ++it_map_expression)
+    {
+      std::string key = it_map_expression->first;
+      std::string map_expression = it_map_expression->second.as_string();
+      std::string final_expression;
+      double final_coefficient = 1.0;
+      // deal with sign
+      if (map_expression[0] == '-') {
+        final_expression = map_expression.substr(1, map_expression.size() - 1);
+        final_coefficient = -1.0;
+      } else
+        final_expression = map_expression;
+
+      std::cout << key << ": " << final_coefficient << std::endl;
+      from_parallel_name_to_state_vector_name[key] = final_expression;
+      coefficient_parallel_joints_.push_back(final_coefficient);
+    }
+  nbOfParallelJoints_ = from_parallel_name_to_state_vector_name.size();
+
+
+  // Prepare joint_state according to robot description.
+  joint_state_.name.resize(static_cast<unsigned long>(nbOfJoints_) + nbOfParallelJoints_);
+  joint_state_.position.resize(static_cast<unsigned long>(nbOfJoints_) + nbOfParallelJoints_);
+
+  // Fill in the name of the joints from the state vector.
+  // and build local map from state name to state vector
+  for (std::size_t i = 0; i < stateVectorMap_.size(); ++i) {
+    joint_state_.name[i] = stateVectorMap_[i];
+
+    from_state_name_to_state_vector[joint_state_.name[i]] = static_cast<int>(i);
+  }
+
+  // Fill in the name of the joints from the parallel joint vector.
+  // and build map from parallel name to state vector
+  std::size_t i = 0;
+  parallel_joints_to_state_vector_.resize(nbOfParallelJoints_);
+  for (std::map<std::string, std::string>::iterator it = from_parallel_name_to_state_vector_name.begin();
+       it != from_parallel_name_to_state_vector_name.end(); it++, i++) {
+    joint_state_.name[i + static_cast<std::size_t>(nbOfJoints_)] = it->first.c_str();
+    parallel_joints_to_state_vector_[i] = from_state_name_to_state_vector[it->second];
+  }
+
+  return 0;
+}
+
+int SotLoaderBasic::parseOptions(int argc, char* argv[]) {
+  po::options_description desc("Allowed options");
+  desc.add_options()("help", "produce help message")("input-file", po::value<string>(), "library to load");
+
+  vm_.clear();
+
+  po::store(po::parse_command_line(argc, argv, desc), vm_);
+  po::notify(vm_);
+
+  if (vm_.count("help")) {
+    cout << desc << "\n";
+    return -1;
+  }
+  if (!vm_.count("input-file")) {
+    cout << "No filename specified\n";
+    return -1;
+  } else
+    dynamicLibraryName_ = vm_["input-file"].as<string>();
+
+  return 0;
+}
+
+void SotLoaderBasic::loadController() {
+  // Load the SotRobotBipedController library.
+  sotRobotControllerLibrary_ = dlopen(dynamicLibraryName_.c_str(), RTLD_LAZY | RTLD_GLOBAL);
+  if (!sotRobotControllerLibrary_) {
+    std::cerr << "Cannot load library: " << dlerror() << '\n';
+    return;
+  }
+
+  // reset errors
+  dlerror();
+
+  // Load the symbols.
+  createSotExternalInterface_t* createSot = reinterpret_cast<createSotExternalInterface_t*>(
+      reinterpret_cast<long>(dlsym(sotRobotControllerLibrary_, "createSotExternalInterface")));
+  const char* dlsym_error = dlerror();
+  if (dlsym_error) {
+    std::cerr << "Cannot load symbol create: " << dlsym_error
+              << " from " << dynamicLibraryName_
+              << '\n';
+    return;
+  }
+
+  // Create robot-controller
+  sotController_ = createSot();
+  cout << "Went out from loadController." << endl;
+}
+
+void SotLoaderBasic::CleanUp() {
+  dynamicgraph::PoolStorage::destroy();
+  // We do not destroy the FactoryStorage singleton because the module will not
+  // be reloaded at next initialization (because Python C API cannot safely
+  // unload a module...).
+  // SignalCaster singleton could probably be destroyed.
+
+  // Load the symbols.
+  destroySotExternalInterface_t* destroySot = reinterpret_cast<destroySotExternalInterface_t*>(
+      reinterpret_cast<long>(dlsym(sotRobotControllerLibrary_, "destroySotExternalInterface")));
+  const char* dlsym_error = dlerror();
+  if (dlsym_error) {
+    std::cerr << "Cannot load symbol destroy: " << dlsym_error << '\n';
+    return;
+  }
+
+  destroySot(sotController_);
+  sotController_ = NULL;
+
+  /// Uncount the number of access to this library.
+  try {
+    if (sotRobotControllerLibrary_ != 0)
+      dlclose(sotRobotControllerLibrary_);
+  }
+  catch(exception &e) {
+    std::throw_with_nested(std::logic_error("SotLoaderBasic::CleanUp() Unable to call dlclose"));
+  }
+}
+
+void SotLoaderBasic::start_dg(const std::shared_ptr<std_srvs::srv::Empty::Request>,
+                              std::shared_ptr<std_srvs::srv::Empty::Response>) {
+  dynamic_graph_stopped_ = false;
+}
+
+void SotLoaderBasic::stop_dg(const std::shared_ptr<std_srvs::srv::Empty::Request>,
+                             std::shared_ptr<std_srvs::srv::Empty::Response>) {
+  dynamic_graph_stopped_ = true;
+}
diff --git a/ros2/tests/CMakeLists.txt b/ros2/tests/CMakeLists.txt
index 82f89d5..ca26786 100644
--- a/ros2/tests/CMakeLists.txt
+++ b/ros2/tests/CMakeLists.txt
@@ -12,41 +12,72 @@ if(BUILD_TESTING)
 
   # Library for sot_external_interface
   add_library(impl_test_sot_external_interface SHARED
-    tests/impl_test_sot_external_interface)
+              impl_test_sot_external_interface)
 
-  target_link_libraries(impl_test_sot_external_interface PUBLIC sot-core::sot-core)
+  target_link_libraries(impl_test_sot_external_interface
+                        PUBLIC sot-core::sot-core)
 
   # Executable for SotLoaderBasic test
-  add_executable(test_sot_loader_basic
-    tests/test_sot_loader_basic.cpp
-  )
-  target_include_directories(test_sot_loader_basic PUBLIC include  "${GMOCK_INCLUDE_DIRS}")
-  target_link_libraries(test_sot_loader_basic sot_loader "${GMOCK_LIBRARIES}" )
+  add_executable(test_sot_loader_basic test_sot_loader_basic.cpp)
+  target_include_directories(
+    test_sot_loader_basic
+    PUBLIC include "${GMOCK_INCLUDE_DIRS}"
+    PRIVATE ${PROJECT_SOURCE_DIR}/ros2/include)
+  target_link_libraries(test_sot_loader_basic sot_loader "${GMOCK_LIBRARIES}")
 
-  add_launch_test(tests/launching_test_sot_loader_basic.py)
+  add_launch_test(launch/launching_test_sot_loader_basic.py)
 
   # Test for class SotLoader
-  add_executable(test_sot_loader
-    tests/test_sot_loader.cpp
-  )
-  target_include_directories(test_sot_loader PUBLIC include "${GMOCK_INCLUDE_DIRS}")
+  add_executable(test_sot_loader test_sot_loader.cpp)
+  target_include_directories(
+    test_sot_loader
+    PUBLIC include "${GMOCK_INCLUDE_DIRS}"
+    PRIVATE ${PROJECT_SOURCE_DIR}/ros2/include)
   target_link_libraries(test_sot_loader sot_loader "${GMOCK_LIBRARIES}")
 
-  add_launch_test(tests/launching_test_sot_loader.py)
+  add_launch_test(launch/launching_test_sot_loader.py)
 
   # Install tests
-  install(TARGETS test_sot_loader_basic
-    test_sot_loader
-    DESTINATION lib/${PROJECT_NAME}
-    )
+  install(TARGETS test_sot_loader_basic test_sot_loader
+          DESTINATION lib/${PROJECT_NAME})
 
   # Install library for tests
-  install(TARGETS impl_test_sot_external_interface
-    DESTINATION lib
-    )
+  install(TARGETS impl_test_sot_external_interface DESTINATION lib)
+
+  install(DIRECTORY launch urdf DESTINATION share/${PROJECT_NAME})
+
+  #
+  # this macro define a unit tests using gtest
+  #
+  macro(create_ros_bridge_unittest test_name dependencies)
+    # Set a general config folder path for all tests
+    set(TEST_CONFIG_PATH ${PROJECT_SOURCE_DIR}/tests/config/)
+
+    # Create the cmake target using ament and gtest.
+    ament_add_gtest(test_${test_name} main.cpp test_${test_name}.cpp)
+
+    # Include dependencies.
+    target_include_directories(
+      test_${test_name}
+      PUBLIC $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
+             $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/demos>
+             $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/tests>
+             $<INSTALL_INTERFACE:include>
+             SYSTEM
+      PUBLIC ${PYTHON_INCLUDE_DIRS} ${EIGEN3_INCLUDE_DIR}
+      PRIVATE ${PROJECT_SOURCE_DIR}/ros2/include)
+
+    # Link the dependecies to it.
+    target_link_libraries(test_${test_name} ros_bridge)
 
-  install(DIRECTORY launch urdf
-    DESTINATION share/${PROJECT_NAME}
-    )
+    # add some preprocessor variable
+    target_compile_definitions(
+      test_${test_name}
+      PUBLIC TEST_CONFIG_PATH="${CMAKE_CURRENT_LIST_DIR}/python_scripts/")
+  endmacro(create_ros_bridge_unittest test_name)
 
+  # C++ unit-tests.
+  find_package(ament_cmake_gtest)
+  create_ros_bridge_unittest(ros_init dynamic_graph_bridge)
+  create_ros_bridge_unittest(ros_interpreter dynamic_graph_bridge)
 endif()
diff --git a/ros2/tests/impl_test_sot_external_interface.cpp b/ros2/tests/impl_test_sot_external_interface.cpp
new file mode 100644
index 0000000..0fae4d8
--- /dev/null
+++ b/ros2/tests/impl_test_sot_external_interface.cpp
@@ -0,0 +1,77 @@
+#include "impl_test_sot_external_interface.hh"
+
+ImplTestSotExternalInterface::ImplTestSotExternalInterface()
+{
+  std::vector<double> ctrl_vector;
+  ctrl_vector.resize(2);
+
+  // Dummy values for the control vector
+  ctrl_vector[0] = 0.0;
+  ctrl_vector[1] = 0.0;
+
+  // Creates the named control vector
+  named_ctrl_vec_.setName("control");
+  named_ctrl_vec_.setValues(ctrl_vector);
+
+  // Creates the base eff vector
+  named_base_ff_vec_.setName("baseff");
+
+  ctrl_vector.resize(7);
+  for( auto i=0;i<6;i++)
+    ctrl_vector[i] = 0.0;
+  ctrl_vector[6] = 0.0;
+  named_base_ff_vec_.setValues(ctrl_vector);
+}
+
+ImplTestSotExternalInterface::~ImplTestSotExternalInterface()
+{}
+
+void ImplTestSotExternalInterface::setupSetSensors
+(std::map<std::string,
+ dynamicgraph::sot::SensorValues> &)
+{
+  std::cout << "ImplTestSotExternalInterface::setupSetSensors" << std::endl;
+}
+
+void ImplTestSotExternalInterface::nominalSetSensors
+(std::map<std::string,
+ dynamicgraph::sot::SensorValues> &)
+{
+  std::cout << "ImplTestSotExternalInterface::nominalSetSensors" << std::endl;
+}
+
+void ImplTestSotExternalInterface::cleanupSetSensors
+(std::map<std::string,
+ dynamicgraph::sot::SensorValues> &)
+{
+  std::cout << "ImplTestSotExternalInterface::cleanupSetSensors" << std::endl;
+}
+
+void ImplTestSotExternalInterface::getControl
+(std::map<std::string,
+ dynamicgraph::sot::ControlValues> & controlValues)
+{
+  // Put the default named_ctrl_vec inside the map controlValues.
+  controlValues["control"] = named_ctrl_vec_;
+  controlValues["baseff"] = named_base_ff_vec_;
+}
+void ImplTestSotExternalInterface::setSecondOrderIntegration
+(void)
+{
+  std::cout << "ImplTestSotExternalInterface::setSecondOrderIntegration" <<  std::endl;
+}
+
+void ImplTestSotExternalInterface::setNoIntegration
+(void)
+{
+  std::cout << "setNoIntegration" << std::endl;
+}
+
+extern "C" {
+  dynamicgraph::sot::AbstractSotExternalInterface *createSotExternalInterface()
+  { return new ImplTestSotExternalInterface; }
+}
+
+extern "C" {
+  void destroySotExternalInterface(dynamicgraph::sot::AbstractSotExternalInterface *p) { delete p; }
+}
diff --git a/ros2/tests/impl_test_sot_external_interface.hh b/ros2/tests/impl_test_sot_external_interface.hh
new file mode 100644
index 0000000..bfca53b
--- /dev/null
+++ b/ros2/tests/impl_test_sot_external_interface.hh
@@ -0,0 +1,39 @@
+#ifndef _DGB_IMPL_TEST_SOT_EXTERNAL_INTEFACE_HH_
+#define _DGB_IMPL_TEST_SOT_EXTERNAL_INTEFACE_HH_
+
+#include <iostream>
+#include <sot/core/abstract-sot-external-interface.hh>
+
+
+class ImplTestSotExternalInterface : public
+dynamicgraph::sot::AbstractSotExternalInterface {
+public:
+  ImplTestSotExternalInterface();
+  virtual ~ImplTestSotExternalInterface();
+
+  virtual void setupSetSensors(std::map<std::string,
+                               dynamicgraph::sot::SensorValues> &) final;
+
+  virtual void nominalSetSensors(std::map<std::string,
+                                 dynamicgraph::sot::SensorValues> &) final;
+
+  virtual void cleanupSetSensors(std::map<std::string,
+                                 dynamicgraph::sot::SensorValues> &) final;
+  virtual void getControl(std::map<std::string,
+                          dynamicgraph::sot::ControlValues> &) final;
+  
+  virtual void setSecondOrderIntegration(void);
+  
+  virtual void setNoIntegration(void);
+
+protected:
+  // Named ctrl vector
+  dynamicgraph::sot::ControlValues named_ctrl_vec_;
+
+  // Named base free flyer vector
+  dynamicgraph::sot::ControlValues named_base_ff_vec_;
+
+};
+
+
+#endif
diff --git a/ros2/tests/launch/__pycache__/launching_test_sot_loader.cpython-38.pyc b/ros2/tests/launch/__pycache__/launching_test_sot_loader.cpython-38.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..38a7e4a70f12f5d39995c8bd4a099fedc28c2e57
GIT binary patch
literal 2062
zcmaJ?OOG5i5VqYtJ<okjHVGjTganZ0vLkXvD6j+&iI#{BLh_~MZF^^W(vQJ*Z!*#B
zX>-j#;4pLK#xcLeCnQe!3q(<<a?d84Y``s-%dc|PSC9T>ZLLF~{q^im!P|uVjgzyj
z0h3$M%`+g9NSYB|kCanCXPo&l;DH}Q9{RDd8Y3S0dDD+9Yvpa;rYIxG!o0&fo)5Eb
z-s3&bH?p;Sov(YIW*dA%ZA>;NTQZW(C**|k{`W+-Wcvw`ZN(-RP6EEIqQ%7}f%T5;
z!g{w}hqIR?-6w<IbI_jb4XE3`C+zsgGA;J217lNL*|aQRee15Mie&t)GKo&7tHp4y
zlnOSlj+Bk3B6%Q2DmG)GRV-7j5?kt7f|5@gqSb`H3*8hzEIA>^#8S&7J)x4xU=*Ct
zV`_srT>wr?SY0%h<e14&Hb!jDWb`{b4s0Z;Y@(I(U_@o-Y2!Gw%>|i<wl!%lI!gj$
zchOstQLv<T?SvdMu(P|zx8<x5d78u{EvDo6K&Nt~cvxw9`1q0>9mIKBq`AmqT^^Kn
z2iJKt0YK~*tMTT5@l9ieRq>IxG0w%5Z~61Fd;<{Qu*%rjls3*vA(eI!fTvV(f6#Ev
z6<n^P9;u|V;viElDl0p!tm~@0%22+wGuN34Epla*HiLB?0BD*aK`dQZPE`TIls+tV
z?n14E)FJLfnRl35ug4g5NsEyS!K0a5yQ9?f>nuG|_jH-Krby7G#&v%N0kiw%eK-vw
z%0y&l(BLeS{G)RQ#z|RNRiNwbm|ywNeHVEXjJp6v0K5VfiKp<eGUgAWKLCCIQNwYW
zt6^S<BPFV#R7WZsJ}mVE14yZ%(aG?Ao*U}Y+#I5DW>{Og8t{pC@ZXuv+?A0kl=j%7
zbI!Qy254b!0wFA7j75}D_L98{n|<V8MFGrLU-%+whrqXJ>tz^*FZ73S>#acdVu0L&
zZnl9ag5>~80R;II0;M2;a131^f!;6eT^}Id2$LoWy7;sqkM7)sZu&sxY(nM~r95R=
z06<Ex_Z(}~fI%G8n7XD-a|OW!xp`3-qqO~1r1rZ~?~f}h%ZJ4vaG_Dzp}q_^T^sMk
zX#p{WzNpC++}ZRHzIIz#+=f+y@ki+68|RhZdH4Jt7}<(rNI4b9t`o<3DXR?mUL5~c
ziLBny*FoZ$$GsY;fxZgFTaS;nn4c%WzSVH=7WDsvdJDD1XosNt$>&*u*G?Pqh_-)g
z6BtV-biw9yNmn@2y+NRH2hzo3YTQQ6+<uhWT_}ZS!1OgZpg%$K4wUO28U;roZ=|{d
zM`|F0$JfrOR^#~kFrcV!pv+H^e1>Fz<joUr0Q(#^et_<Uw*rmx!DYuv;{r1?u2mJO
zMO}CwoC4XK7E(Qmr!zY)3%voS`Ko5@tg3=HnqPXmkeqJv?RtI3ocUpxx<nzKyuZ2!
ziF$g+S15k<{PR1js%Yx!7J*Wrs0(wUAGq!r1$@T27UH%mAign5Tz^_(y<LU4&wg+H
n`(SddFY8GB3N1F*fj~CX7K0Bd@IBU}@Ud19(ay*HMnwMw_>DID

literal 0
HcmV?d00001

diff --git a/ros2/tests/launch/__pycache__/launching_test_sot_loader_basic.cpython-38.pyc b/ros2/tests/launch/__pycache__/launching_test_sot_loader_basic.cpython-38.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..14e3f9104e814af03429f3496e89f183a7955e00
GIT binary patch
literal 2103
zcmbVN&2Jnv6t_LIv$LN`wrQmjsE`VUl~7jn!~vm}P%1$cDpFKwE+g;Q*`3XNEVegI
zs?CMu+W%qqh{S;#|BJ7jdgg*aw7ln?wCM*IJo5ANv;F-1dmr;dr_&_R{`l=ze$XJ~
zcbr^oHJIFju73d{h@ctCX+$aVEMpOKqZfH@^sBKJ)!aOAqnFk5FbdrnKlAcN)Np()
zYv!$}<@g|L=bfli>Fq{c*`2IS)<s=}XXKPd8+VClh~^m)P01#`Q!lzCYm3b#0eMTb
zLEc`;Vee(Z_DH|;6f7ovYW@4%jCX<AMCO|^E%s(ZZBjEcX<2}L;~t+C$@nX&6O~RY
zp}$)S35#1JY2qnQ4){pMdd!uKMXF?CN_Cvz4d=}VCWO8NU7rCl<dmEcLk$!36h6VW
zi14<^37yl0w<P+8@qePGW&%^6yB&*AkR@=7#*&;c(-c(rq9y`FK{Q4zrJ^|pI%1;r
zq;}$&)`HAE)1Gt|-6er>ZLz*2BX3E~#wpoH6xzGdrZ_Hmo+j}~@##1os#J_*<j<7Y
ze|$xZhH;)2Y0k4)mBZ3(qddB7q&BfGO`MfnNEHvcP7@mdLQ)nFqIGS!k@2C!A<p?U
z3MK%o`Lr5uMjK9BEG~drv-Ju{+muH#nHfIJqz%fMna+%D$$XZfR%4EBbIKLZrIAYa
zdkV2t3`qdQ2J$G?M%(Uft))VT-BGGk)-R{BP#$jav{7)KRQsjMt*;~(3S-#@nRB-7
zuErQ2nieDLLo{Tz^R1NA+gW-j@2fJgb)KMhZCl@ig5w9}1K15J%7kaS??)^X(X}@o
z8Yg98WPxF0#?jUPT(_6tc>q2~1L7%MD7F5Xs4cMbuY<w3%;g|2_@U&pfsltX8$2r2
zfd&laK&xc%Hop#3sc#POQF^d?VKv|tZ*}VS^w?e<$wDfJJ^KBE)7}MJ^k+Z_3%n*}
z&lzLSfY#cS(e>w{$6$0HYtrBu`#Wg+jM0#W|G=)g|EGEn&c2c;P8pC}(Dfz|Ng^r;
zloCjhGXf>VgTVD|55e9m%{>=^Ut$0z=-cy#JO;)Nbln3oXA?4~sO1U6LIPU6-KSXJ
z`V3-Wg|DrfG?x%^klUBJi=T%)HD8x%Z#*-id{p#3>uZ_qt9Rg}ZQ!{$Eg+82FDrtm
z*mVcz3m2BfZIEh=QbHeJf7AHw*Z1$jBf~g`<dku2n{k|%VwNG_j^iI^JX@`(4?*LF
z%bgi$fw~6d7Ib%MNZtH@LA`<2V#GrLe&@0=aqj@#*pNrG9n$N-7&4&?Hm6JXBLJM~
zZr@YrOGp`qskUn?=FU-ScA%8%K2z6WgZco;YfyF_G?3K>a!1I!uw@0L_xSxcOk3gj
zVl$wqZlKPOkbI2f6C^LMcnMe^7Vbb_0mc;0`&Y0mnQ@j5U5T;btVj(Sq%Og7R27Qt
zsuZ|aMps^~9_NeE=1RV;FZ^M&t4OYfMZM}AWa;@CSC*nJ2m+`WX(8lMJUuq!ve>SQ
zrLKw|NWm!U=Ug4w)&&DxirEl>+Tl<TF)VCvT4DvSV%nv?d+ytCvEk>d(EDNy!NK!w
TOyTNl)72Mx0c~FI)dKo2Kng{+

literal 0
HcmV?d00001

diff --git a/ros2/tests/launch/launching_test_sot_loader.py b/ros2/tests/launch/launching_test_sot_loader.py
new file mode 100644
index 0000000..788c222
--- /dev/null
+++ b/ros2/tests/launch/launching_test_sot_loader.py
@@ -0,0 +1,78 @@
+# Copyright 2021 LAAS-CNRS
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import os
+import sys
+import unittest
+
+import ament_index_python
+
+import launch
+import launch.actions
+
+import launch_testing
+import launch_testing.actions
+from launch.substitutions import PathJoinSubstitution
+
+from launch import LaunchDescription
+from launch_ros.actions import Node
+from ament_index_python.packages import get_package_share_directory
+
+import pytest
+
+
+@pytest.mark.launch_test
+def generate_test_description():
+    ld = LaunchDescription()
+
+    robot_description_content_path =  PathJoinSubstitution(
+                [
+                    get_package_share_directory("dynamic_graph_bridge"),
+                    "urdf",
+                    "dgb_minimal_robot.urdf",
+                ]
+            )
+    robot_description_content = open(robot_description_content_path.perform(None)).read()
+
+    params = { "state_vector_map": [ "joint1", "joint2"],
+               "robot_description": robot_description_content};
+
+    terminating_process = Node(
+         package="dynamic_graph_bridge",
+         executable="test_sot_loader",
+         output="screen",
+         emulate_tty=True,
+         parameters=[params],
+    )
+
+    return (
+        launch.LaunchDescription([
+            terminating_process,
+            launch_testing.util.KeepAliveProc(),
+            launch_testing.actions.ReadyToTest(),
+        ]), locals()
+    )
+
+class TestSotLoaderBasic(unittest.TestCase):
+
+    def test_termination(self, terminating_process, proc_info):
+        proc_info.assertWaitForShutdown(process=terminating_process, timeout=(10))
+
+@launch_testing.post_shutdown_test()
+class TestProcessOutput(unittest.TestCase):
+
+    def test_exit_code(self, proc_info):
+        # Check that all processes in the launch (in this case, there's just one) exit
+        # with code 0
+        launch_testing.asserts.assertExitCodes(proc_info)
diff --git a/ros2/tests/launch/launching_test_sot_loader_basic.py b/ros2/tests/launch/launching_test_sot_loader_basic.py
new file mode 100755
index 0000000..0d0b3b8
--- /dev/null
+++ b/ros2/tests/launch/launching_test_sot_loader_basic.py
@@ -0,0 +1,74 @@
+# Copyright 2021 LAAS-CNRS
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from pathlib import Path
+import unittest
+import launch
+import launch.actions
+import launch_testing
+import launch_testing.actions
+from launch.substitutions import PathJoinSubstitution
+from launch import LaunchDescription
+from launch_ros.actions import Node
+from ament_index_python.packages import get_package_share_directory
+import pytest
+
+
+@pytest.mark.launch_test
+def generate_test_description():
+    ld = LaunchDescription()
+
+    robot_description_content_path = (
+        Path(get_package_share_directory("dynamic_graph_bridge")) /
+        "urdf" /
+        "dgb_minimal_robot.urdf"
+    )
+    assert(robot_description_content_path.exists())
+    robot_description_content = open(
+        PathJoinSubstitution(str(robot_description_content_path)).perform(None)
+    ).read()
+    terminating_process = Node(
+        package="dynamic_graph_bridge",
+        executable="test_sot_loader_basic",
+        output="screen",
+        emulate_tty=True,
+        parameters=[
+            {"state_vector_map": ["joint1", "joint2"]},
+            {"robot_description": robot_description_content},
+        ],
+    )
+
+    return (
+        launch.LaunchDescription(
+            [
+                terminating_process,
+                launch_testing.util.KeepAliveProc(),
+                launch_testing.actions.ReadyToTest(),
+            ]
+        ),
+        locals(),
+    )
+
+
+class TestSotLoaderBasic(unittest.TestCase):
+    def test_termination(self, terminating_process, proc_info):
+        proc_info.assertWaitForShutdown(process=terminating_process, timeout=(10))
+
+
+@launch_testing.post_shutdown_test()
+class TestProcessOutput(unittest.TestCase):
+    def test_exit_code(self, proc_info):
+        # Check that all processes in the launch (in this case, there's just one) exit
+        # with code 0
+        launch_testing.asserts.assertExitCodes(proc_info)
diff --git a/ros2/tests/main.cpp b/ros2/tests/main.cpp
new file mode 100644
index 0000000..f4adc6a
--- /dev/null
+++ b/ros2/tests/main.cpp
@@ -0,0 +1,24 @@
+/**
+ * @file main.cpp
+ * @author Maximilien Naveau (maximilien.naveau@gmail.com)
+ * @license License BSD-3-Clause
+ * @copyright Copyright (c) 2019, New York University and Max Planck
+ * Gesellschaft.
+ * @date 2019-05-22
+ *
+ * @brief Main file that runs all unittest using gtest
+ * @see
+ * https://git-amd.tuebingen.mpg.de/amd-clmc/ci_example/wikis/catkin:-how-to-implement-unit-tests
+ */
+
+#include <gtest/gtest.h>
+
+#include <rclcpp/rclcpp.hpp>
+
+int main(int argc, char **argv)
+{
+    rclcpp::init(argc, argv);
+    ::testing::InitGoogleTest(&argc, argv);
+    return RUN_ALL_TESTS();
+    rclcpp::shutdown();
+}
diff --git a/ros2/tests/python_scripts/simple_add.py b/ros2/tests/python_scripts/simple_add.py
new file mode 100644
index 0000000..5d8b44e
--- /dev/null
+++ b/ros2/tests/python_scripts/simple_add.py
@@ -0,0 +1 @@
+a = 1 + 1 +1
diff --git a/ros2/tests/python_scripts/simple_add_fail.py b/ros2/tests/python_scripts/simple_add_fail.py
new file mode 100644
index 0000000..ee2ab51
--- /dev/null
+++ b/ros2/tests/python_scripts/simple_add_fail.py
@@ -0,0 +1 @@
+a = 1 + 1 + b
diff --git a/ros2/tests/test_common.hpp b/ros2/tests/test_common.hpp
new file mode 100644
index 0000000..008a183
--- /dev/null
+++ b/ros2/tests/test_common.hpp
@@ -0,0 +1,167 @@
+/**
+ * @file
+ * @license BSD 3-clause
+ * @copyright Copyright (c) 2020, New York University and Max Planck
+ * Gesellschaft
+ *
+ * @brief Common include and declaration for the tests.
+ */
+
+#pragma once
+
+#include <gtest/gtest.h>
+#include <signal.h>
+#include <sys/types.h>
+
+#include <chrono>
+#include <thread>
+
+// We use another class of this package which has an independant unittest.
+#include "dynamic_graph_bridge/ros.hpp"
+
+using namespace std::chrono_literals;
+using namespace dynamic_graph_bridge;
+
+/**
+ * @brief Global ros node name used unitquely for the unit-tests.
+ */
+const std::string ROS_NODE_NAME = "test_dgm";
+
+void start_dg_ros_service()
+{
+    // Service name.
+    std::string service_name = "/dynamic_graph_bridge/start_dynamic_graph";
+    // Create a client from a temporary node.
+    RosNodePtr ros_node = get_ros_node(ROS_NODE_NAME);
+    auto client = ros_node->create_client<std_srvs::srv::Empty>(service_name);
+    ASSERT_TRUE(client->wait_for_service(1s));
+
+    // Fill the command message.
+    std_srvs::srv::Empty::Request::SharedPtr request =
+        std::make_shared<std_srvs::srv::Empty::Request>();
+    std::shared_future<std_srvs::srv::Empty::Response::SharedPtr> response;
+    // Call the service.
+    response = client->async_send_request(request);
+    // Wait for the answer.
+    ASSERT_TRUE(response.valid());
+    // wait_for_response(response);
+    ASSERT_TRUE(rclcpp::spin_until_future_complete(ros_node, response) ==
+                rclcpp::executor::FutureReturnCode::SUCCESS);
+}
+
+void stop_dg_ros_service()
+{
+    // Service name.
+    std::string service_name = "/dynamic_graph_bridge/stop_dynamic_graph";
+    // Create a client from a temporary node.
+    RosNodePtr ros_node = get_ros_node(ROS_NODE_NAME);
+    auto client = ros_node->create_client<std_srvs::srv::Empty>(service_name);
+    ASSERT_TRUE(client->wait_for_service(1s));
+
+    // Fill the command message.
+    std_srvs::srv::Empty::Request::SharedPtr request =
+        std::make_shared<std_srvs::srv::Empty::Request>();
+    std::shared_future<std_srvs::srv::Empty::Response::SharedPtr> response;
+    // Call the service.
+    response = client->async_send_request(request);
+    // Wait for the answer.
+    ASSERT_TRUE(response.valid());
+    // wait_for_response(response);
+    ASSERT_TRUE(rclcpp::spin_until_future_complete(ros_node, response) ==
+                rclcpp::executor::FutureReturnCode::SUCCESS);
+}
+
+void start_run_python_command_ros_service(const std::string& cmd,
+                                          std::string& result,
+                                          std::string& standarderror,
+                                          std::string& standardoutput)
+{
+    // Service name.
+    std::string service_name = "/dynamic_graph_bridge/run_python_command";
+    // Create a client from a temporary node.
+    RosNodePtr ros_node = get_ros_node(ROS_NODE_NAME);
+    auto client =
+        ros_node->create_client<dynamic_graph_bridge_msgs::srv::RunPythonCommand>(service_name);
+    ASSERT_TRUE(client->wait_for_service(1s));
+
+    // Fill the command message.
+    dynamic_graph_bridge_msgs::srv::RunPythonCommand::Request::SharedPtr request =
+        std::make_shared<dynamic_graph_bridge_msgs::srv::RunPythonCommand::Request>();
+    std::shared_future<dynamic_graph_bridge_msgs::srv::RunPythonCommand::Response::SharedPtr>
+        response;
+    request->input = cmd;
+    // Call the service.
+    response = client->async_send_request(request);
+    // Wait for the answer.
+    ASSERT_TRUE(response.valid());
+    // wait_for_response(response);
+    ASSERT_TRUE(rclcpp::spin_until_future_complete(ros_node, response) ==
+                rclcpp::executor::FutureReturnCode::SUCCESS);
+    // Get the results.
+    result = response.get()->result;
+    standarderror = response.get()->standarderror;
+    standardoutput = response.get()->standardoutput;
+}
+
+void start_run_python_script_ros_service(const std::string& file_name,
+                                         std::string& result)
+{
+    // Service name.
+    std::string service_name = "/dynamic_graph_bridge/run_python_file";
+    // Create a client from a temporary node.
+    RosNodePtr ros_node = get_ros_node(ROS_NODE_NAME);
+    auto client =
+        ros_node->create_client<dynamic_graph_bridge_msgs::srv::RunPythonFile>(service_name);
+    ASSERT_TRUE(client->wait_for_service(1s));
+
+    // Fill the command message.
+    dynamic_graph_bridge_msgs::srv::RunPythonFile::Request::SharedPtr request =
+        std::make_shared<dynamic_graph_bridge_msgs::srv::RunPythonFile::Request>();
+    std::shared_future<dynamic_graph_bridge_msgs::srv::RunPythonFile::Response::SharedPtr>
+        response;
+    request->input = file_name;
+    // Call the service.
+    response = client->async_send_request(request);
+    // Wait for the answer.
+    ASSERT_TRUE(response.valid());
+    // wait_for_response(response);
+    ASSERT_TRUE(rclcpp::spin_until_future_complete(ros_node, response) ==
+                rclcpp::executor::FutureReturnCode::SUCCESS);
+    // Get the results.
+    result = response.get()->result;
+}
+
+void display_services(
+    std::map<std::string, std::vector<std::string> >& list_service_and_types)
+{
+    for (std::map<std::string, std::vector<std::string> >::const_iterator
+             const_it = list_service_and_types.begin();
+         const_it != list_service_and_types.end();
+         ++const_it)
+    {
+        std::cerr << const_it->first << "\t\t\t";
+        for (unsigned i = 0; i < const_it->second.size(); ++i)
+        {
+            std::cerr << std::right;
+            std::cerr << const_it->second[i] << std::endl;
+        }
+    }
+}
+
+void display_services()
+{
+    std::map<std::string, std::vector<std::string> > list_service_and_types =
+        get_ros_node("display_services")->get_service_names_and_types();
+    for (std::map<std::string, std::vector<std::string> >::const_iterator
+             const_it = list_service_and_types.begin();
+         const_it != list_service_and_types.end();
+         ++const_it)
+    {
+        std::cerr << const_it->first << "\t\t\t";
+        for (unsigned i = 0; i < const_it->second.size(); ++i)
+        {
+            std::cerr << std::right;
+            std::cerr << const_it->second[i] << std::endl;
+        }
+    }
+}
\ No newline at end of file
diff --git a/ros2/tests/test_import.py b/ros2/tests/test_import.py
new file mode 100755
index 0000000..b919565
--- /dev/null
+++ b/ros2/tests/test_import.py
@@ -0,0 +1,27 @@
+#!/usr/bin/env python
+
+from dynamic_graph.ros import RosImport
+
+ri = RosImport('rosimport')
+
+ri.add('double', 'doubleS', 'doubleT')
+ri.add('vector', 'vectorS', 'vectorT')
+ri.add('matrix', 'matrixS', 'matrixT')
+
+ri.doubleS.value = 42.
+ri.vectorS.value = (
+    42.,
+    42.,
+)
+ri.matrixS.value = (
+    (
+        42.,
+        42.,
+    ),
+    (
+        42.,
+        42.,
+    ),
+)
+
+ri.trigger.recompute(ri.trigger.time + 1)
diff --git a/ros2/tests/test_ros_init.cpp b/ros2/tests/test_ros_init.cpp
new file mode 100644
index 0000000..b671466
--- /dev/null
+++ b/ros2/tests/test_ros_init.cpp
@@ -0,0 +1,191 @@
+/**
+ * @file test_get_ros_node.cpp
+ * @author Maximilien Naveau (maximilien.naveau@gmail.com)
+ * @license License BSD-3-Clause
+ * @copyright Copyright (c) 2019, New York University and Max Planck
+ * Gesellschaft.
+ * @date 2019-05-22
+ */
+
+#include "test_common.hpp"
+
+/***************************
+ * SETUP THE TESTING CLASS *
+ ***************************/
+
+using namespace std::chrono_literals;
+using namespace dynamic_graph_bridge;
+
+/**
+ * @brief This is the test environment
+ */
+class TestRosInit : public ::testing::Test
+{
+    /**
+     * @brief SetUp, is executed before the unit tests
+     */
+    void SetUp()
+    {
+        ros_spin_non_blocking();
+    }
+
+    /**
+     * @brief TearDown, is executed after the unit tests
+     */
+    void TearDown()
+    {
+        ros_clean();
+    }
+};
+
+/**
+ * @brief This is the test environment
+ */
+class TestRosInitGlobalVar : public ::testing::Test
+{
+};
+
+/*****************
+ * Start Testing *
+ *****************/
+
+/**
+ * Here we check that we can create a ros node
+ */
+TEST_F(TestRosInitGlobalVar, test_create_node)
+{
+    /* Setup */
+    std::string node_name = "my_global_node";
+    get_ros_node(node_name);
+
+    /* Test */
+    ASSERT_TRUE(ros_node_exists(node_name));
+}
+
+/**
+ * Here we check that we still have the ros node created (global variable)
+ */
+TEST_F(TestRosInitGlobalVar, test_global_variable_deleted_after_tear_down)
+{
+    /* Setup */
+    std::string node_name = "my_global_node";
+
+    /* Test */
+    ASSERT_TRUE(ros_node_exists(node_name));
+}
+
+/**
+ * Here we check that the destruction of the variable
+ */
+TEST_F(TestRosInitGlobalVar, test_delete_global_variable)
+{
+    /* Setup */
+    std::string node_name = "my_global_node";
+    ros_shutdown(node_name);
+
+    /* Test */
+    ASSERT_FALSE(ros_node_exists(node_name));
+}
+
+/**
+ * Here we check if we can destruct the object several time
+ */
+TEST_F(TestRosInit, test_multiple_shutdown_call)
+{
+    /* Setup */
+    std::string node_name = "my_global_node";
+
+    /* Test */
+    ASSERT_NO_THROW(for (unsigned i = 0; i < 20; ++i) {
+        ros_shutdown(node_name);
+        std::this_thread::sleep_for(std::chrono::milliseconds(1));
+    });
+}
+
+/**
+ * @brief Dummy function to be called as ros services
+ *
+ * @return true
+ */
+bool simple_service(std_srvs::srv::Empty::Request::SharedPtr,
+                    std_srvs::srv::Empty::Response::SharedPtr)
+{
+    return true;
+}
+
+/**
+ * Check if services are available
+ */
+TEST_F(TestRosInit, test_services_available)
+{
+    /* Setup */
+    // create a node and a service
+    RosNodePtr n0 = get_ros_node("node_0");
+    ros_add_node_to_executor("node_0");
+    std::string service_name = "/simple_service";
+    rclcpp::Service<std_srvs::srv::Empty>::SharedPtr server =
+        n0->create_service<std_srvs::srv::Empty>(service_name, &simple_service);
+
+    /* Test */
+    rclcpp::Client<std_srvs::srv::Empty>::SharedPtr client =
+        n0->create_client<std_srvs::srv::Empty>(service_name);
+    ASSERT_TRUE(client->wait_for_service(1s));
+}
+
+/**
+ * Check if services are not longer available after shutdown
+ */
+TEST_F(TestRosInit, test_services_shut_down)
+{
+    /* Setup */
+    std::string service_name = "/simple_service";
+
+    // create a node and a service and shutdown the node
+    {
+        RosNodePtr n0 = get_ros_node("node_0");
+        ros_add_node_to_executor("node_0");
+        rclcpp::Service<std_srvs::srv::Empty>::SharedPtr server =
+            n0->create_service<std_srvs::srv::Empty>(service_name,
+                                                     &simple_service);
+        ros_shutdown("node_0");
+    }
+
+    // create a client to this service
+    RosNodePtr n1 = get_ros_node("node_1");
+    rclcpp::Client<std_srvs::srv::Empty>::SharedPtr client =
+        n1->create_client<std_srvs::srv::Empty>(service_name);
+
+    /* Test */
+    ASSERT_FALSE(client->wait_for_service(100ms));
+}
+
+/**
+ * Check if shutdown without name kills all created nodes.
+ */
+TEST_F(TestRosInit, test_killall_nodes)
+{
+    /* Setup */
+    // creates tones of nodes
+    unsigned int nb_nodes = 20;
+    for (unsigned int i = 0; i < nb_nodes; ++i)
+    {
+        {
+            std::ostringstream os;
+            os << "node_" << i;
+            get_ros_node(os.str());
+        }
+    }
+
+    // kill them all
+    ros_clean();
+
+    /* Test if they are all killed */
+    for (unsigned int i = 0; i < nb_nodes; ++i)
+    {
+        {
+            std::ostringstream os;
+            os << "node_" << i;
+            ASSERT_FALSE(ros_node_exists(os.str()));
+        }
+    }
+}
diff --git a/ros2/tests/test_ros_interpreter.cpp b/ros2/tests/test_ros_interpreter.cpp
new file mode 100644
index 0000000..7966e70
--- /dev/null
+++ b/ros2/tests/test_ros_interpreter.cpp
@@ -0,0 +1,272 @@
+/**
+ * @file test_ros_interpreter.cpp
+ * @author Maximilien Naveau (maximilien.naveau@gmail.com)
+ * @license License BSD-3-Clause
+ * @copyright Copyright (c) 2019, New York University and Max Planck
+ * Gesellschaft.
+ * @date 2019-05-22
+ */
+
+#include <iomanip>
+
+#include "test_common.hpp"
+#include "dynamic_graph_bridge/ros_python_interpreter_server.hpp"
+
+/***************************
+ * SETUP THE TESTING CLASS *
+ ***************************/
+
+using namespace std::chrono_literals;
+using namespace dynamic_graph_bridge;
+
+/**
+ * @brief This is the test environment
+ */
+class TestRosInterpreter : public ::testing::Test
+{
+protected:
+    /**
+     * @brief SetUp, is executed before the unit tests
+     */
+    void SetUp()
+    {
+        // Create the ros node
+        node_name_ = "unittest_ros_python_interpreter";
+        run_python_command_service_name_ =
+            "/dynamic_graph_bridge/run_python_command";
+        run_python_file_service_name_ =
+            "/dynamic_graph_bridge/run_python_file";
+        ros_spin_non_blocking();
+    }
+
+    /**
+     * @brief TearDown, is executed after the unit tests
+     */
+    void TearDown()
+    {
+        // delete the ros node
+        ros_clean();
+    }
+    /**
+     * @brief Node name
+     */
+    std::string node_name_;
+
+    /**
+     * @brief Name of the ros service.
+     */
+    std::string run_python_command_service_name_;
+
+    /**
+     * @brief Name of the ros service.
+     */
+    std::string run_python_file_service_name_;
+};
+
+/*****************
+ * Start Testing *
+ *****************/
+
+TEST_F(TestRosInterpreter, test_constructor_no_throw)
+{
+    /* setup */
+
+    /* test */
+    ASSERT_NO_THROW(RosPythonInterpreterServer rpi;);
+}
+
+TEST_F(TestRosInterpreter, test_destructor_no_throw)
+{
+    ASSERT_NO_THROW({ RosPythonInterpreterServer rpi; });
+}
+
+TEST_F(TestRosInterpreter, test_run_cmd_not_available_upon_construction)
+{
+    /* Setup. */
+
+    // Init the class to test.
+    RosPythonInterpreterServer rpi;
+
+    // Fetch the information using ROS.
+    std::map<std::string, std::vector<std::string>> list_service_and_types;
+    RosNodePtr ros_node = get_ros_node(node_name_);
+    bool simple_service_exists = false;
+
+    // Check if the service exists.
+    list_service_and_types = ros_node->get_service_names_and_types();
+    display_services(list_service_and_types);
+    simple_service_exists =
+        list_service_and_types.find(run_python_command_service_name_) !=
+        list_service_and_types.end();
+
+    /* Test. */
+    ASSERT_FALSE(simple_service_exists);
+}
+
+TEST_F(TestRosInterpreter, test_run_file_not_available_upon_construction)
+{
+    /* Setup. */
+
+    // Init the class to test.
+    RosPythonInterpreterServer rpi;
+
+    // Fetch the information using ROS.
+    std::map<std::string, std::vector<std::string>> list_service_and_types;
+    RosNodePtr ros_node = get_ros_node(node_name_);
+    bool simple_service_exists = false;
+
+    // Check if the service exists.
+    list_service_and_types = ros_node->get_service_names_and_types();
+    display_services(list_service_and_types);
+    simple_service_exists =
+        list_service_and_types.find(run_python_file_service_name_) !=
+        list_service_and_types.end();
+
+    /* Test. */
+    ASSERT_FALSE(simple_service_exists);
+}
+
+TEST_F(TestRosInterpreter, test_run_cmd_available_after_init)
+{
+    /* Setup. */
+
+    // Init the class to test.
+    RosPythonInterpreterServer rpi;
+    rpi.start_ros_service();
+
+    // print the service available
+    RosNodePtr ros_node = get_ros_node(node_name_);
+    std::map<std::string, std::vector<std::string>> list_service_and_types =
+        ros_node->get_service_names_and_types();
+    display_services(list_service_and_types);
+
+    // Fetch the information using ROS.
+    auto client = get_ros_node(node_name_)
+                      ->create_client<dynamic_graph_bridge_msgs::srv::RunPythonCommand>(
+                          "/dynamic_graph_bridge/run_python_command");
+
+    /* Test. */
+    ASSERT_TRUE(client->wait_for_service(1s));
+}
+
+TEST_F(TestRosInterpreter, test_run_file_available_after_init)
+{
+    /* Setup. */
+
+    // Init the class to test.
+    RosPythonInterpreterServer rpi;
+    rpi.start_ros_service();
+
+    // Fetch the information using ROS.
+    auto client = get_ros_node(node_name_)
+                      ->create_client<dynamic_graph_bridge_msgs::srv::RunPythonFile>(
+                          "/dynamic_graph_bridge/run_python_file");
+
+    /* Test. */
+    ASSERT_TRUE(client->wait_for_service(1s));
+}
+
+TEST_F(TestRosInterpreter, test_call_run_command_result)
+{
+    /* Setup. */
+
+    // Create the ros python interpreter.
+    RosPythonInterpreterServer rpi;
+    rpi.start_ros_service();
+
+    // Create and call the clients.
+    std::string cmd = "1 + 1";
+    std::string result = "";
+    std::string standarderror = "";
+    std::string standardoutput = "";
+    start_run_python_command_ros_service(
+        cmd, result, standarderror, standardoutput);
+
+    /* Tests. */
+    ASSERT_EQ(result, "2");
+}
+
+TEST_F(TestRosInterpreter, test_call_run_command_standardoutput)
+{
+    /* Setup. */
+
+    // Create the ros python interpreter.
+    RosPythonInterpreterServer rpi;
+    rpi.start_ros_service();
+
+    // Create and call the clients.
+    std::string cmd = "print(\"Banana\")";
+    std::string result = "";
+    std::string standarderror = "";
+    std::string standardoutput = "";
+    start_run_python_command_ros_service(
+        cmd, result, standarderror, standardoutput);
+
+    /* Tests. */
+    ASSERT_EQ(standardoutput, "Banana\nprint(\"Banana\")\nOutput:Banana\n\n");
+}
+
+TEST_F(TestRosInterpreter, test_call_run_command_standarderror)
+{
+    /* Setup. */
+
+    // Create the ros python interpreter.
+    RosPythonInterpreterServer rpi;
+    rpi.start_ros_service();
+
+    // Create and call the clients.
+    std::string cmd = "a";
+    std::string result = "";
+    std::string standarderror = "";
+    std::string standardoutput = "";
+    start_run_python_command_ros_service(
+        cmd, result, standarderror, standardoutput);
+
+    /* Tests. */
+    ASSERT_EQ(
+        standarderror,
+        "Traceback (most recent call last):\n  File \"<string>\", line 1, "
+        "in <module>\nNameError: name 'a' is not defined\n");
+}
+
+TEST_F(TestRosInterpreter, test_call_run_script_result)
+{
+    /* Setup. */
+
+    // Create the ros python interpreter.
+    RosPythonInterpreterServer rpi;
+    rpi.start_ros_service();
+
+    // Create and call the clients.
+    std::string file_name = TEST_CONFIG_PATH + std::string("simple_add.py");
+    std::string result = "";
+    start_run_python_script_ros_service(file_name, result);
+
+    /* Tests. */
+    ASSERT_EQ(result, "");
+}
+
+TEST_F(TestRosInterpreter, test_call_run_script_standarderror)
+{
+    /* Setup. */
+
+    // Create the ros python interpreter.
+    RosPythonInterpreterServer rpi;
+    rpi.start_ros_service();
+
+    // Create and call the clients.
+    std::string file_name =
+        TEST_CONFIG_PATH + std::string("simple_add_fail.py");
+    std::string result = "";
+    start_run_python_script_ros_service(file_name, result);
+
+    // Prepare the test.
+    std::string error_first_part = "a = 1 + 1 + b";
+    std::string error_second_part = "NameError: name \'b\' is not defined";
+    std::size_t found_first_part = result.find(error_first_part);
+    std::size_t found_second_part = result.find(error_second_part);
+
+    /* test */
+    ASSERT_TRUE(found_first_part != std::string::npos);
+    ASSERT_TRUE(found_second_part != std::string::npos);
+}
diff --git a/ros2/tests/test_sot_loader.cpp b/ros2/tests/test_sot_loader.cpp
new file mode 100644
index 0000000..b914ee2
--- /dev/null
+++ b/ros2/tests/test_sot_loader.cpp
@@ -0,0 +1,93 @@
+
+#include <gmock/gmock.h>
+#include "dynamic_graph_bridge/ros.hpp"
+#include "dynamic_graph_bridge/sot_loader.hh"
+
+class MockSotLoaderTest: public ::testing::Test {
+  
+public:
+
+  class MockSotLoader : public SotLoader {
+  public:
+    ~MockSotLoader(){}
+
+    void generateEvents() {
+      usleep(20000);
+      std::cerr << "Start Dynamic Graph " << std::endl;
+      dynamic_graph_stopped_ = false;
+
+      usleep(20000);
+      std::cerr << "Stop Dynamic Graph " << std::endl;
+      dynamic_graph_stopped_ = true;
+    }
+    
+    void testLoadController() {
+      // Set input  call
+      int argc=2;
+      char *argv[2];
+      char argv1[30]="mocktest";
+      argv[0]=argv1;
+      char argv2[60]="--input-file=libimpl_test_sot_external_interface.so";
+      argv[1]=argv2;
+      parseOptions(argc,argv);
+
+      std::string finalname("libimpl_test_sot_external_interface.so");
+      EXPECT_TRUE(finalname == dynamicLibraryName_);
+      
+      // Performs initialization of libimpl_test_sot_external_interface.so
+      loadController();
+      EXPECT_TRUE(sotRobotControllerLibrary_ != 0);
+      EXPECT_TRUE(sotController_ != nullptr);
+      // initialize start/stop and runCommand services
+      initializeServices();
+      
+      // Constructor should default freeFlyerPose
+      EXPECT_TRUE( freeFlyerPose_.header.frame_id == std::string("odom"));
+      EXPECT_TRUE( freeFlyerPose_.child_frame_id == std::string("base_link"));
+
+      // Initialie publication of sot joint states
+      // and base eff
+      initPublication();
+
+      // Start the control loop thread.
+      startControlLoop();
+      
+      // Start the thread generating events.
+      std::thread local_events(&MockSotLoader::generateEvents,this);
+
+      // Wait for each threads.
+      SotLoader::lthread_join(); // Wait 100 ms
+      local_events.join();      
+    }
+  };
+  
+public:
+  MockSotLoader* mockSotLoader_ptr_;
+  
+  void SetUp() {
+    mockSotLoader_ptr_ = new MockSotLoader();
+    mockSotLoader_ptr_->initialize(); 
+  }
+
+  void TearDown() {
+    delete mockSotLoader_ptr_;
+    mockSotLoader_ptr_ = nullptr;
+  }
+
+};
+
+TEST_F(MockSotLoaderTest,TestLoadController)
+{
+  mockSotLoader_ptr_->testLoadController();
+}
+
+  
+int main(int argc, char **argv) {
+  ::testing::InitGoogleTest(&argc, argv);
+  rclcpp::init(argc, argv);
+  
+  int r=RUN_ALL_TESTS();
+
+  rclcpp::shutdown();
+  return r;
+}
diff --git a/ros2/tests/test_sot_loader_basic.cpp b/ros2/tests/test_sot_loader_basic.cpp
new file mode 100644
index 0000000..20b0b1b
--- /dev/null
+++ b/ros2/tests/test_sot_loader_basic.cpp
@@ -0,0 +1,184 @@
+
+#include <gmock/gmock.h>
+#include "dynamic_graph_bridge/ros.hpp"
+#include "dynamic_graph_bridge/sot_loader_basic.hh"
+
+class MockSotLoaderBasicTest: public ::testing::Test {
+
+public:
+
+  class MockSotLoaderBasic: public SotLoaderBasic {
+  public:
+    virtual ~MockSotLoaderBasic(){}
+
+    void checkStateVectorMap() {
+      readSotVectorStateParam();
+      ASSERT_EQ(static_cast<int>(stateVectorMap_.size()),2);
+      ASSERT_EQ(nbOfJoints_, 2);
+      std::string aJoint1("joint1");
+      EXPECT_TRUE(aJoint1 == stateVectorMap_[0]);
+      std::string aJoint2("joint2");
+      EXPECT_TRUE(aJoint2 == stateVectorMap_[1]);
+    }
+
+    void testParseOptions() {
+
+      // Void call
+      int argc=1;
+      char argv1[30]="mocktest";
+      char *argv[3];
+      argv[0]=argv1;
+      EXPECT_EQ(parseOptions(argc,argv),-1);
+
+      // Test help call
+      argc=2;
+      char argv2[30]="--help";
+      argv[1]=argv2;
+      EXPECT_EQ(parseOptions(argc,argv),-1);
+
+      // Test input file
+      char argv3[60]="--input-file=libimpl_test_sot_external_interface.so";
+      argv[1]=argv3;
+      EXPECT_EQ(parseOptions(argc,argv),0);
+
+      // Check that the file is what we specified
+      std::string finalname("libimpl_test_sot_external_interface.so");
+      EXPECT_TRUE(finalname == dynamicLibraryName_);
+    }
+
+    void testInitializeRosNode() {
+      int argc=1;
+      char *argv[1];
+      char argv1[30]="mocktest";
+      argv[0] = argv1;
+      parseOptions(argc,argv);
+      initializeServices();
+      EXPECT_TRUE(service_start_ != 0);
+      EXPECT_TRUE(service_stop_ != 0);
+      initPublication();
+      EXPECT_TRUE(joint_pub_ != 0);
+    }
+
+    void testJointStatesPublication() {
+      int argc=1;
+      char *argv[1];
+      char argv1[30]="mocktest";
+      argv[0] = argv1;
+      parseOptions(argc,argv);
+
+    }
+
+    void testInitialization() {
+
+      // Set input arguments
+      int argc=2;
+      char *argv[2];
+
+      char argv1[30]="mocktest";
+      argv[0]=argv1;
+      char argv2[60]="--input-file=libimpl_test_sot_external_interface.so";
+      argv[1]=argv2;
+      parseOptions(argc,argv);
+
+      std::string finalname("libimpl_test_sot_external_interface.so");
+      EXPECT_TRUE(finalname == dynamicLibraryName_);
+
+      // Performs initializatio of libimpl_test_sot_external_interface.so
+      loadController();
+      EXPECT_TRUE(sotRobotControllerLibrary_ != 0);
+      EXPECT_TRUE(sotController_ != nullptr);
+    }
+
+    void test_start_stop_dg() {
+      std::shared_ptr<std_srvs::srv::Empty::Request> empty_rqst;
+      std::shared_ptr<std_srvs::srv::Empty::Response> empty_resp;
+      start_dg(empty_rqst,empty_resp);
+      EXPECT_FALSE(dynamic_graph_stopped_);
+
+      stop_dg(empty_rqst,empty_resp);
+      EXPECT_TRUE(dynamic_graph_stopped_);
+    }
+
+    void test_cleanup() {
+      CleanUp();
+      EXPECT_TRUE(sotController_ == NULL);
+      std::cout << "sotController_: " << sotController_
+                << std::endl;
+
+      // Set input arguments
+      int argc=2;
+      char *argv[2];
+
+      char argv1[30]="mocktest";
+      argv[0]=argv1;
+      char argv2[60]="--input-file=libimpl_test_sot_external_interface.so";
+      argv[1]=argv2;
+      parseOptions(argc,argv);
+
+      std::string finalname("libimpl_test_sot_external_interface.so");
+      EXPECT_TRUE(finalname == dynamicLibraryName_);
+
+      // Performs initializatio of libimpl_test_sot_external_interface.so
+      loadController();
+      // Remove
+      CleanUp();
+    }
+  };
+
+public:
+
+  MockSotLoaderBasic* mockSotLoaderBasic_ptr_;
+
+  void SetUp() {
+    mockSotLoaderBasic_ptr_ = new MockSotLoaderBasic();
+    mockSotLoaderBasic_ptr_->initialize();
+  }
+
+  void TearDown() {
+    delete mockSotLoaderBasic_ptr_;
+    mockSotLoaderBasic_ptr_ = nullptr;
+  }
+
+};
+
+TEST_F(MockSotLoaderBasicTest, TestReadingParameters)
+{
+  mockSotLoaderBasic_ptr_->checkStateVectorMap();
+}
+
+TEST_F(MockSotLoaderBasicTest,ParseOptions)
+{
+  mockSotLoaderBasic_ptr_->testParseOptions();
+}
+
+TEST_F(MockSotLoaderBasicTest,TestInitialization)
+{
+  mockSotLoaderBasic_ptr_->testInitialization();
+}
+
+TEST_F(MockSotLoaderBasicTest,TestStartStopDG)
+{
+  mockSotLoaderBasic_ptr_->test_start_stop_dg();
+}
+
+TEST_F(MockSotLoaderBasicTest,TestInitializeRosNode)
+{
+  mockSotLoaderBasic_ptr_->testInitializeRosNode();
+}
+
+TEST_F(MockSotLoaderBasicTest,CleanUp)
+{
+  mockSotLoaderBasic_ptr_->test_cleanup();
+}
+
+int main(int argc, char **argv) {
+  ::testing::InitGoogleTest(&argc, argv);
+  rclcpp::init(argc, argv);
+
+  int r=RUN_ALL_TESTS();
+
+  std::cout << "ros shutdown" << std::endl;
+  rclcpp::shutdown();
+  std::cout << "ros shutdown done" << std::endl;
+  return r;
+}
diff --git a/ros2/tests/urdf/dgb_minimal_robot.urdf b/ros2/tests/urdf/dgb_minimal_robot.urdf
new file mode 100644
index 0000000..e15cb93
--- /dev/null
+++ b/ros2/tests/urdf/dgb_minimal_robot.urdf
@@ -0,0 +1,98 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- =================================================================================== -->
+<!-- |    This document was autogenerated by xacro from minimal_robot.urdf.xacro       | -->
+<!-- |    EDITING THIS FILE BY HAND IS NOT RECOMMENDED                                 | -->
+<!-- =================================================================================== -->
+<robot name="MinimalRobot">
+  <!-- Used for fixing robot -->
+  <link name="world"/>
+  <joint name="base_joint" type="fixed">
+    <origin rpy="0 0 0" xyz="0 0 0"/>
+    <parent link="world"/>
+    <child link="base_link"/>
+  </joint>
+  <link name="base_link">
+    <inertial>
+      <mass value="0.01"/>
+      <origin xyz="0 0 0"/>
+      <inertia ixx="0.001" ixy="0.0" ixz="0.0" iyy="0.001" iyz="0.0" izz="0.001"/>
+    </inertial>
+    <visual>
+      <origin rpy="0 0 0" xyz="0 0 0"/>
+      <geometry>
+        <cylinder length="0.2" radius="0.1"/>
+      </geometry>
+      <material name="DarkGrey">
+        <color rgba="0.4 0.4 0.4 1.0"/>
+      </material>
+    </visual>
+    <collision>
+      <origin rpy="0 0 0" xyz="0 0 0"/>
+      <geometry>
+        <cylinder length="1" radius="0.1"/>
+      </geometry>
+    </collision>
+  </link>
+  <joint name="joint1" type="revolute">
+    <origin rpy="-1.57079632679 0 0" xyz="0 0 0.2"/>
+    <parent link="base_link"/>
+    <child link="link1"/>
+    <limit effort="0.1" lower="-3.14159265359" upper="3.14159265359" velocity="0.2"/>
+  </joint>
+  <link name="link1">
+    <inertial>
+      <mass value="0.01"/>
+      <origin xyz="0 0 0"/>
+      <inertia ixx="0.001" ixy="0.0" ixz="0.0" iyy="0.001" iyz="0.0" izz="0.001"/>
+    </inertial>
+    <visual>
+      <origin rpy="0 0 0" xyz="0 0 0"/>
+      <geometry>
+        <cylinder length="1" radius="0.1"/>
+      </geometry>
+      <material name="DarkGrey">
+        <color rgba="0.4 0.4 0.4 1.0"/>
+      </material>
+    </visual>
+    <collision>
+      <origin rpy="0 0 0" xyz="0 0 0"/>
+      <geometry>
+        <cylinder length="1" radius="0.1"/>
+      </geometry>
+    </collision>
+  </link>
+  <joint name="joint2" type="revolute">
+    <origin rpy="1.57079632679 0 0" xyz="0 0 0.9"/>
+    <parent link="link1"/>
+    <child link="link2"/>
+    <limit effort="0.1" lower="-3.14159265359" upper="3.14159265359" velocity="0.2"/>
+  </joint>
+  <link name="link2">
+    <inertial>
+      <mass value="0.01"/>
+      <origin xyz="0 0 0"/>
+      <inertia ixx="0.001" ixy="0.0" ixz="0.0" iyy="0.001" iyz="0.0" izz="0.001"/>
+    </inertial>
+    <visual>
+      <origin rpy="0 0 0" xyz="0 0 0"/>
+      <geometry>
+        <cylinder length="1" radius="0.1"/>
+      </geometry>
+      <material name="DarkGrey">
+        <color rgba="0.4 0.4 0.4 1.0"/>
+      </material>
+    </visual>
+    <collision>
+      <origin rpy="0 0 0" xyz="0 0 0"/>
+      <geometry>
+        <cylinder length="1" radius="0.1"/>
+      </geometry>
+    </collision>
+  </link>
+  <joint name="tool_joint" type="fixed">
+    <origin rpy="0 0 0" xyz="0 0 1"/>
+    <parent link="link2"/>
+    <child link="tool_link"/>
+  </joint>
+  <link name="tool_link">
+  </link>
-- 
GitLab