ROSをROS2へ

この記事では既存のROSパッケージをROS2で使用するためのコンバーティングについて日本語でまとめています。
内容はROS2の[Miguration Guide](https://github.com/ros2/ros2/wiki/Migration-Guide)を参考にさせていただきました。
開発環境はUbuntu 16.04を使用しています。
ROS1のバージョンはkineticですが、ほかのバージョンでも差異はないはずです。
#**既存のROSパッケージをROS2へ移植**
##**ROS**
ROS2へ移植する元となるROSパッケージ、その実行について説明します。
### **ROSコードの準備**
[ROS Tutorials](http://wiki.ros.org/ROS/Tutorials/WritingPublisherSubscriber%28c%2B%2B%29)で用いられるシンプルなROSパッケージ`talker`を準備します。
catkinワークスペース（ここでは`~/ros1_talker`）内にtalkerパッケージを作ります。ファイル階層は以下の通りです。

```
$cd ~/ros1_talker
$find .
.
./src
./src/talker
./src/talker/package.xml
./src/talker/CMakeLists.txt
./src/talker/talker.cpp
``` 

また、3つのファイルの中身は以下の通りです。
それぞれコピペします。
`src/talker/package.xml`:

```manifest:package.xml
<package>
  <name>talker</name>
  <version>0.0.0</version>
  <description>talker</description>
  <maintainer email="gerkey@osrfoundation.org">Brian Gerkey</maintainer>
  <license>Apache 2.0</license>
  <buildtool_depend>catkin</buildtool_depend>
  <build_depend>roscpp</build_depend>
  <build_depend>std_msgs</build_depend>
  <run_depend>roscpp</run_depend>
  <run_depend>std_msgs</run_depend>
</package>
```

`/src/talker/CMakeLists.txt`：

```:CMakeLists.txt
cmake_minimum_required(VERSION 2.8.3)
project(talker)
find_package(catkin REQUIRED COMPONENTS roscpp std_msgs)
catkin_package()
include_directories(${catkin_INCLUDE_DIRS})
add_executable(talker talker.cpp)
target_link_libraries(talker ${catkin_LIBRARIES})
install(TARGETS talker
  RUNTIME DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION})
```

`src/talker/talker.cpp`：

```c++:talker.cpp
#include <sstream>
#include "ros/ros.h"
#include "std_msgs/String.h"
int main(int argc, char **argv)
{
  ros::init(argc, argv, "talker");
  ros::NodeHandle n;
  ros::Publisher chatter_pub = n.advertise<std_msgs::String>("chatter", 1000);
  ros::Rate loop_rate(10);
  int count = 0;
  std_msgs::String msg;
  while (ros::ok())
  {
    std::stringstream ss;
    ss << "hello world " << count++;
    msg.data = ss.str();
    ROS_INFO("%s", msg.data.c_str());
    chatter_pub.publish(msg);
    ros::spinOnce();
    loop_rate.sleep();
  }
  return 0;
}
```

###**ROSコードのビルド**
まずROS環境下で`talker`が動くか試します。
sourceコマンドでセットアップファイルを実行して環境変数設定を行ったのち、`catkin_make install`をつかいパッケージをビルドします。

```
. /opt/ros/kinetic/setup.bash
cd ~/ros1_talker
catkin_make install
```

###**ROSノードの実行**
セットアップファイルを`catkin`インストールツリーから実行し`roscore`を起動させます。(セットアップファイルは`/opt/ros/kinetic/setup.bash`からでも実行できます)`roscore`がすでに起動してある場合はこの操作は必要ありません。

```
. ~/ros1_talker/install/setup.bash
roscore
```

さらにシェルを開き、もう一度セットアップファイルを実行したのち、`rosrun`をつかい`catkin`インストールスペースからノードを実行します。(この場合ノードはワークスペース内にある必要がります)

```
. ~/ros1_talker/install/setup.bash
rosrun talker talker
```

成功すればhello worldとメッセージが出力されます。
これでros環境下で動くことが確認できました。

```
hello world 0
hello world 1
hello world 2
hello world 3
hello world 4
hello world 5
:
```

##**ROS2への移植**
ROS2用のワークスペースをつくります。
ここへROSパッケージのソースをコピーします。

```
mkdir -p ~/ros2_talker/src
cd ~/ros2_talker
cp -a ~/ros1_talker/src/talker src
```

###**`talker.cpp`の変更**
ROSノード内のC++コードを変更していきます。rclcppと呼ばれるROS2C++ライブラリではroscppとは異なるAPIを提供していますが、2つのライブラリは似ているため変更は複雑ではありません。

####**ヘッダのインクルード**
`roscpp`ライブラリAPIへアクセスする`ros/ros.h`の代わりに`rclcpp/rclcpp.hpp`をインクルードします。

```
//#include "ros/ros.h"
#include "rclcpp/rclcpp.hpp"
```

std_msgs/Stringメッセージを出力するため、メッセージ型のヘッダをインクルードします。

```
//#include "std_msgs/String.h"
#include "std_msgs/msg/string.hpp"
```

####**ライブラリコールの変更**
ROSではノード名をライブラリの初期化呼び出しへ渡し、ノードの初期化をしていました。また`ros::NodeHandle`を作成することによってノードの起動をおこなっていました。
ROS2では初期化を行い、ノードオブジェクトの作成時にノードに名前をつけます。(ROS2はC++11をつかうので変数宣言時に型名の代わりとしてautoを指定しています)

```
//  ros::init(argc, argv, "talker");
//  ros::NodeHandle n;
    rclcpp::init(argc, argv);
    auto node = rclcpp::Node::make_shared("talker");
```

Publisher、rateオブジェクトは名前空間とメソッド名が変更されているほかは、かなり似ています。Publisherの定義ではキューの数として渡していた整数の代わりにqosプロファイルを渡しメッセージ配信処理を柔軟に行えるようにしています。
ここでは、`rmw`で定義された名前空間を持たないグローバルな既定のプロファイル`rmw_qos_profile_default`を渡しています。（Cで記述されている）

```
//  ros::Publisher chatter_pub = n.advertise<std_msgs::String>("chatter", 1000);
//  ros::Rate loop_rate(10);
  auto chatter_pub = node->create_publisher<std_msgs::msg::String>("chatter",
    rmw_qos_profile_default);
  rclcpp::Rate loop_rate(10);
```

スマートポインタを用いて送信するメッセージの保存をします。

```
//  std_msgs::String msg;
  auto msg = std::make_shared<std_msgs::msg::String>();
```

Publishループ内で`data`フィールドにアクセスするには`msg`がスマートポインタなので、アロー演算子`->`をつかいます。

```
//    msg.data = ss.str();
    msg->data = ss.str();
```

データの送信でも、Publisherがスマートポインタとなっているのでアロー演算子をつかいます。

```
//    chatter_pub.publish(msg);
    chatter_pub->publish(msg);
```

ROS_INFO、spin等の定義もすべて変更すると以下のようになります。

```c++:talker.cpp
#include <sstream>
// #include "ros/ros.h"
#include "rclcpp/rclcpp.hpp"
// #include "std_msgs/String.h"
#include "std_msgs/msg/string.hpp"
int main(int argc, char **argv)
{
//  ros::init(argc, argv, "talker");
//  ros::NodeHandle n;
  rclcpp::init(argc, argv);
  auto node = rclcpp::node::Node::make_shared("talker");
//  ros::Publisher chatter_pub = n.advertise<std_msgs::String>("chatter", 1000);
//  ros::Rate loop_rate(10);
  auto chatter_pub = node->create_publisher<std_msgs::msg::String>("chatter", rmw_qos_profile_default);
  rclcpp::rate::Rate loop_rate(10);
  int count = 0;
//  std_msgs::String msg;
  auto msg = std::make_shared<std_msgs::msg::String>();
//  while (ros::ok())
  while (rclcpp::ok())
  {
    std::stringstream ss;
    ss << "hello world " << count++;
//    msg.data = ss.str();
    msg->data = ss.str();
//    ROS_INFO("%s", msg.data.c_str());
    printf("%s\n", msg->data.c_str());
//    chatter_pub.publish(msg);
    chatter_pub->publish(msg);
//    ros::spinOnce();
    rclcpp::spin_some(node);
    loop_rate.sleep();
  }
  return 0;
}
```

###**`package.xml`の変更**
ROS2のマニフェストはバージョン2のみサポートしています。まずフォーマットバージョンを定義します。

```
<!-- <package> -->
<package format="2">
```

ROS2は`catkin`に代わり`ament`バージョンを使っています。`buildtool_depend`タグを変更します。

```
<!--  <buildtool_depend>catkin</buildtool_depend> -->
  <buildtool_depend>ament_cmake</buildtool_depend>
```

ビルド等の依存関係を変更していきます。また、ビルドタイプ宣言をします。

```
<!--  <build_depend>roscpp</build_depend> -->
  <build_depend>rclcpp</build_depend>
  <build_depend>rmw_implementation</build_depend>
<!--  <run_depend>roscpp</run_depend> -->
  <exec_depend>rclcpp</exec_depend>
  <exec_depend>rmw_implementation</exec_depend>
<!--  <run_depend>std_msgs</run_depend> -->
  <exec_depend>std_msgs</exec_depend>

 <export>
    <build_type>ament_cmake</build_type>
  </export>
```

変更後の`package.xml`は以下の通りです。

```:package.xml
<!-- <package> -->
<package format="2">
  <name>talker</name>
  <version>0.0.0</version>
  <description>talker</description>
  <maintainer email="gerkey@osrfoundation.org">Brian Gerkey</maintainer>
  <license>Apache License 2.0</license>
<!--  <buildtool_depend>catkin</buildtool_depend> -->
  <buildtool_depend>ament_cmake</buildtool_depend>
<!--  <build_depend>roscpp</build_depend> -->
  <build_depend>rclcpp</build_depend>
  <build_depend>rmw_implementation</build_depend>
  <build_depend>std_msgs</build_depend>
<!--  <run_depend>roscpp</run_depend> -->
  <exec_depend>rclcpp</exec_depend>
  <exec_depend>rmw_implementation</exec_depend>
<!--  <run_depend>std_msgs</run_depend> -->
  <exec_depend>std_msgs</exec_depend>
  <export>
    <build_type>ament_cmake</build_type>
  </export>
</package>
```

###**CMakeLists.txtの変更**
ROS2ではC++11をつかうので以下の行を追加して有効にします。

```
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
```

ROSでは`catkin`パッケージを探す際にコンポーネントしてほかのパッケージを指定しました。
ROS2では`ament_cmake`から個別にパッケージを探し出します。

```
#find_package(catkin REQUIRED COMPONENTS roscpp std_msgs)
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(rmw_implementation REQUIRED)
find_package(std_msgs REQUIRED)
```

CMakeでは`catkin_package()`を呼び出してパッケージを登録していました。これを`ament_package()`に変更し、さらにターゲットを行ったあとに記述します。

```
# catkin_package()
# ファイルのボトムに記す:
ament_package()
```

依存パッケージは、すべてインクルードパスを指定しなければいけません。
以下のように指定しておきます。

```
#include_directories(${catkin_INCLUDE_DIRS})
include_directories(${rclcpp_INCLUDE_DIRS}
                    ${rmw_implementation_INCLUDE_DIRS}
                    ${std_msgs_INCLUDE_DIRS})
```

また、それぞれリンクするパッケージライブラリを追加します。`target_link_libraries`で追加することができますが、これはターゲット名も指定するので`add_executable`のうしろにもってきます。

```
#target_link_libraries(talker ${catkin_LIBRARIES})
target_link_libraries(talker
                      ${rclcpp_LIBRARIES}
                      ${rmw_implementation_LIBRARIES}
                      ${std_msgs_LIBRARIES})
```

インストールターゲットを追加します。`bin`フォルダ内に実行ファイルを生成します。`catkin`では`CATKIN_PACKAGE_BIN_DESTINATION`のように変数を定義していました。

```
#install(TARGETS talker
#  RUNTIME DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION})
install(TARGETS talker RUNTIME DESTINATION bin)
```

変更後の`CMakeLists.txt`は以下の通りです。

```:CMakeLists.txt
#CMakeのVERSIONは最新にしてください
cmake_minimum_required(VERSION 3.5)
project(talker)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
#find_package(catkin REQUIRED COMPONENTS roscpp std_msgs)
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(rmw_implementation REQUIRED)
find_package(std_msgs REQUIRED)
#catkin_package()
#include_directories(${catkin_INCLUDE_DIRS})
include_directories(${rclcpp_INCLUDE_DIRS}
                    ${rmw_implementation_INCLUDE_DIRS}
                    ${std_msgs_INCLUDE_DIRS})
add_executable(talker talker.cpp)
#target_link_libraries(talker ${catkin_LIBRARIES})
target_link_libraries(talker
                      ${rclcpp_LIBRARIES}
                      ${rmw_implementation_LIBRARIES}
                      ${std_msgs_LIBRARIES})
#install(TARGETS talker
#  RUNTIME DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION})
install(TARGETS talker RUNTIME DESTINATION bin)
ament_package()
```

###**ROS2コードのビルド**
環境設定ファイルを実行し、`ament build`をつかいパッケージをビルドします。

```
. ~/ros2_ws/install/setup.bash
cd ~/ros2_talker
ament build
```

###**ROS2ノードの実行**
`CMakeLists.txt`ファイルの変更で`talker`の実行ファイルを`bin`にインストールしました。セットアップファイルを実行したあと、`talker`を名前ディレクトリから直接呼び出すことができます。

```
. ~/ros2_ws/install/setup.bash
~/ros2_talker/install/bin/talker
```

うまくいけば以下のように表示されます。

```
hello world 0
hello world 1
hello world 2
hello world 3
hello world 4
hello world 5
:
```

以上で終わりです。
説明の誤り、意見等ありましたらコメントよろしくお願いします。

#**参考サイト**
https://github.com/ros2/ros2/wiki/Migration-Guide