SPRESENSEでrviz2上のUDRFモデルを動かす

最近、ROS2を触り始めました。microROS を使って SPRESENSE でプログラミングが出来るようになったのですが、ロボットを作るのがなかなか手が動かない。そこでモチベーションをあげるために、ホストPC上のシミュレーションモデルをSPRESENSEで動かせるか試してみました。

![ROS_SIMARM_CONTROL_SPR.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/598183/90bfd526-03f2-6fb2-8bc2-f75fdc7a062b.png)


# URDFのモデルを準備する
URDFとはPC上でロボットをモデリングするためのXMLベースの記述言語のことです。ロボットを作るのは時間がかかります。一方で制御ソフトウェアの開発も時間がかかってしまいます。ロボットを作ってから制御ソフト開発となると完成までかなり時間がかかってしまいます。そこで、ROSではロボットをシミュレーションする環境が用意されました。

それだけでなく、ロボット完成後もロボットの動きと連動してシミュレーションされたロボットを動かすこともできますので、ロボットの挙動がどのようになっているか把握しやすくなっています。

URDFについての詳細は次の資料が役に立つと思いますので参照してください。

・[urdf_tutorial: Learning URDF Step by Step](https://github.com/ros/urdf_tutorial)
・[MathWorks:URDF入門](https://jp.mathworks.com/help/sm/ug/urdf-model-import.html)
・[マニピュレータ型ロボットのURDF作成](https://gbiggs.github.io/rosjp_urdf_tutorial_text/manipulator_urdf.html)
・[移動型ロボットのURDF作成](https://gbiggs.github.io/rosjp_urdf_tutorial_text/mobile_robot_urdf.html)
・[ROS練習用のロボットを作る](https://qiita.com/yoneken/items/ed2e5edf3aa4e0d8d2e3)



# urdf_tutorial をビルドする
urdf を ros2 で使えるようにするため、urdf_tutorial というプロジェクトをダウンロードしコンパイルします。

ターミナルから次のコマンドでダウンロード＆ビルドができます。（すでにROS2 humbleの環境をインストール済であることが前提です）

```console
~$ source /opt/ros/humble/setup.bash
~$ mkdir -p ros2_ws/src
~/ros2_ws$ cd ros2_ws
~/ros2_ws$ git clone -b ros2 https://github.com/ros/urdf_tutorial.git src/urdf_tutorial
~/ros2_ws$ colcon build --packages-select urdf_tutorial
~/ros2_ws$ source ./install/setup.bash
~/ros2_ws$ ros2 launch urdf_tutorial display.launch.py model:=./src/urdf_tutorial/urdf/07-physics.urdf
```

最後の一行は、./src/urdf_tutorial/urdf/070phisics.urdf と言う URDF で記述されたロボットのモデルを rviz2 で立ち上げるコマンドです。次の画面が出てきたら正常に urdf_tutorial がビルドできています。

![r2d2_urdf.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/598183/41a16f56-d4ef-fa69-88bc-60cbba5d9386.png)

このモデルはちょっと複雑なので、もう少し単純化したモデルがほしいところです。でも、URDFをゼロから作るのは大変なので、アールティ社さんの作られた教育用ロボットアーム「CRAME+」のモデルを使わせていただきました。

・[ROSを使用したCRANE+の動かし方　その8](https://www.rt-shop.jp/blog/archives/6711)

こちらのロボットは関節が４つ、アーム（手を含む）が５つで構成されたモデルです。関節を「joint」,アームを「link」と呼びます。動かすときはjointを操作します。

![ROS_SIMARM_CONTROL.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/598183/f7fc4705-7952-16e3-797b-6cbba9bf4766.png)

```xml:10-arm.urdf
<?xml version="1.0"?>
<robot name="crane">
 
  <link name="base_link">
    <visual>
      <origin xyz="0 0 0.0227" rpy="0 0 0" />
      <geometry>
    <box size="0.0499 0.032 0.0454" />
      </geometry>
      <material name="red">
    <color rgba=".8 0 0 1" />
      </material>
    </visual>
  </link>
 
  <joint name="joint1" type="revolute">
    <parent link="base_link"/>
    <child link="link1"/>
    <origin xyz="0.0145 0 0.0454" rpy="0 0 0" />
    <axis xyz="0 0 1" />
    <limit lower="-2.61" upper="2.61" effort="0.5" velocity="6.0" />
  </joint>
 
  <link name="link1">
    <visual>
      <origin xyz="0 0 0.013" rpy="0 0 0" />
      <geometry>
        <box size="0.024 0.0475 0.026" />
      </geometry>
      <material name="red" />
    </visual>
 
    <visual>
      <origin xyz="0 0 0.026" rpy="1.57079632675 0 0" />
      <geometry>
        <cylinder radius="0.012" length="0.0475" />
      </geometry>
      <material name="red" />
    </visual>
  </link>
 
  <joint name="joint2" type="revolute">
    <parent link="link1"/>
    <child link="link2"/>
    <origin xyz="0 0 0.026" rpy="0 0 0" />
    <axis xyz="0 1 0" />
    <limit lower="-1.75" upper="1.75" effort="0.5" velocity="6.0" />
  </joint>
 
  <link name="link2">
    <visual>
      <origin xyz="0 0 0" rpy="1.57079632675 0 0" />
      <geometry>
        <cylinder radius="0.012" length="0.0475" />
      </geometry>
      <material name="red" />
    </visual>
 
    <visual>
      <origin xyz="0 0 0.0415" rpy="0 0 0" />
      <geometry>
        <box size="0.024 0.0475 0.083" />
      </geometry>
      <material name="red" />
    </visual>
 
    <visual>
      <origin xyz="0 0 0.083" rpy="1.57079632675 0 0" />
      <geometry>
        <cylinder radius="0.012" length="0.0475" />
      </geometry>
      <material name="red" />
    </visual>
  </link>
 
  <joint name="joint3" type="revolute">
    <parent link="link2"/>
    <child link="link3"/>
    <origin xyz="0 0 0.083" rpy="0 0 0" />
    <axis xyz="0 -1 0" />
    <limit lower="-2.55" upper="2.55" effort="0.5" velocity="6.0" />
  </joint>
 
  <link name="link3">
    <visual>
      <origin xyz="0 0 0" rpy="1.57079632675 0 0" />
      <geometry>
        <cylinder radius="0.012" length="0.0475" />
      </geometry>
      <material name="red" />
    </visual>
 
    <visual>
      <origin xyz="0 0 0.04675" rpy="0 0 0" />
      <geometry>
        <box size="0.024 0.0475 0.0935" />
      </geometry>
      <material name="red" />
    </visual>
 
    <visual>
      <origin xyz="0 0 0.0935" rpy="1.57079632675 0 0" />
      <geometry>
        <cylinder radius="0.012" length="0.0475" />
      </geometry>
      <material name="red" />
    </visual>
  </link>
 
  <joint name="joint4" type="revolute">
    <parent link="link3"/>
    <child link="link4"/>
    <origin xyz="0 0 0.0935" rpy="0 0 0" />
    <axis xyz="0 -1 0" />
    <limit lower="-1.80" upper="1.80" effort="0.5" velocity="6.0" />
  </joint>
 
  <link name="link4">
    <visual>
      <origin xyz="0 0 0" rpy="1.57079632675 0 0" />
      <geometry>
        <cylinder radius="0.012" length="0.0475" />
      </geometry>
      <material name="red" />
    </visual>
 
    <visual>
      <origin xyz="0 0 0.0227" rpy="0 0 0" />
      <geometry>
        <box size="0.024 0.0475 0.061" />
      </geometry>
      <material name="red" />
    </visual>
 
    <visual>
      <origin xyz="0 0.03295 0.065727241" rpy="-0.52359878 0 0" />
      <geometry>
        <box size="0.034 0.0014 0.019" />
      </geometry>
      <material name="red" />
    </visual>
 
    <visual>
      <origin xyz="0 0.0377 0.09665" rpy="0 0 0" />
      <geometry>
        <box size="0.034 0.0014 0.0454" />
      </geometry>
      <material name="red" />
    </visual>
  </link>
 
  <joint name="joint5" type="revolute">
    <parent link="link4"/>
    <child link="link5"/>
    <origin xyz="0 -0.0145 0.045" rpy="0 0 0" />
    <axis xyz="-1 0 0" />
    <limit lower="-0.69" upper="0.69" effort="0.5" velocity="6.0" />
  </joint>
 
  <link name="link5">
    <visual>
      <origin xyz="0 0 0" rpy="0 1.57079632675 0" />
      <geometry>
        <cylinder radius="0.012" length="0.0475" />
      </geometry>
      <material name="red" />
    </visual>
 
    <visual>
      <origin xyz="0 -0.013 0" rpy="0 0 0" />
      <geometry>
        <box size="0.0475 0.026 0.024" />
      </geometry>
      <material name="red" />
    </visual>
 
    <visual>
      <origin xyz="0 -0.0267 0.023" rpy="0 0 0" />
      <geometry>
        <box size="0.034 0.0014 0.070" />
      </geometry>
      <material name="red" />
    </visual>
 
    <visual>
      <origin xyz="0 -0.0166252378 0.07545" rpy="-0.52359878 0 0" />
      <geometry>
        <box size="0.034 0.0014 0.040299" />
      </geometry>
      <material name="red" />
    </visual>
  </link>
 </robot>
```

こちらの URDF モデルをコピーして ~/ros2/src/urdf_tutorial/urdf/10-arm.urdf として保存します。次のコマンドで起動してみるとアームなロボットのモデルが表示されます。

```console
~/ros2_ws$ ros2 launch urdf_tutorial display.launch.py model:=./src/urdf_tutorial/urdf/10-arm.urdf
```

![CARNE_ARM.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/598183/3789ec86-97de-1904-6219-1f8fefcbff3b.png)



# URDF を制御するためトピックを調べる
トピックがどうなるか見てみましょう。最初は urdf を立ち上げる前の状態です。

```console
~/ros2_ws$ ros2 topic list
/parameter_events
/rosout
```

コンソールで次のコマンドを打って URDF のモデルを立ち上げます。

```console
~/ros2_ws$ ros2 launch urdf_tutorial display.launch.py model:=./src/urdf_tutorial/urdf/10-arm.urdf
```

別のコンソールを立ち上げて、トピックの様子を見てみましょう。

```console
~$ source /opt/ros2/humble/setup.bash
~$ ros2 topic list
/joint_states
/parameter_events
/robot_description
/rosout
/tf
/tf_static
```

いくつか新しいトピックが現れました。この中の "joint_states"は、次のUI "Joint State Publisher" が生成しているトピックです。画面中のレバーを動かすと、URDFのモデルを動かすことができます。

![Joint_State_Publisher.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/598183/70e175b4-b9ac-21f7-1724-7303623dc602.png)

ということは、SPRESENSE から "Joint State Publisher" が送るトピックの内容を送ればよいということが分かります。

# Joint1 を動かすための情報を確認する
ここで、Joint1 を動かすために Joint1 がどうなっているか確認をしてみます。Joint1 の URDF の定義を見てみましょう。

```xml
  <joint name="joint1" type="revolute">
    <parent link="base_link"/>
    <child link="link1"/>
    <origin xyz="0.0145 0 0.0454" rpy="0 0 0" />
    <axis xyz="0 0 1" />
    <limit lower="-2.61" upper="2.61" effort="0.5" velocity="6.0" />
  </joint>
```
"parent link" は、接続元のアーム（link）の名前を指定しています。
"chile link" は、接続先のアーム（link）の名前を指定しています。
"origin" は、関節の場所と傾きを指定しています。
"axis" は、回転軸を指定しています。
"limit" は、関節の回転角の最大値・最小値、トルク、回転速度を指定します。
limit から Joint1 は、-2.61[rad]（-150度）から+2.61[rad]（+150度）の範囲で回転し、最大 0.5[N] のトルク、回転速度 6.0 [rad/sec] となっています。

ですので、Joint1 を動かすには、joint_stateトピックに回転する角度をメッセージとして投げてあげればよさそうです。

そうと分かれば、トピックの構造を理解して SPRESENSE から必要な情報をなげるだけです。 トピック joint_state の型は、["sensor_msgs/JointState.msg"](http://docs.ros.org/en/melodic/api/sensor_msgs/html/msg/JointState.html) で次のように定義されています。

```c
Header header

string[] name
float64[] position
float64[] velocity
float64[] effort
```
このメッセージは複数のジョイントを指定できるようになっているため配列となっています。

"name" にはジョイント名を指定します。
"position" は、各ジョイントの回転角度を指定します。
"velocity" は、各ジョイントの回転速度を指定します。
"effort" は、各ジョイントの回転トルクを指定します。
Header 構造体の定義は[こちら](http://docs.ros.org/en/melodic/api/std_msgs/html/msg/Header.html)にありますので参照してください。

これで Joint1 を動かす方法については目処がついてきました。


# SPRESENSE に Joint1 を動かすプログラムを実装する
組み込み向けのROS2ライブラリとして micro-ROS というライブラリが公開されています。SPRESENSE 用には次の github に Arduino IDE 環境向けのライブラリが用意されています。

https://github.com/TE-YoshinoriOota/micro_ros_arduino

サンプルや解説についてはここにあります。

https://github.com/TE-YoshinoriOota/Spresense-microROS-Seminar

これらを参考に、 Joint1 を動かすプログラムを SPRESENSE に実装してみました。

```c++
#include <micro_ros_arduino.h>

#include <stdio.h>
#include <rcl/rcl.h>
#include <rcl/error_handling.h>
#include <rclc/rclc.h>
#include <rclc/executor.h>

#include <sensor_msgs/msg/joint_state.h>

static rcl_publisher_t publisher;
static rclc_executor_t executor;
static rclc_support_t support;
static rcl_allocator_t allocator;
static rcl_node_t node;
static rcl_timer_t timer;

static sensor_msgs__msg__JointState jstate;

#define RCCHECK(fn) { rcl_ret_t temp_rc = fn; if((temp_rc != RCL_RET_OK)){error_loop();}}
#define RCSOFTCHECK(fn) { rcl_ret_t temp_rc = fn; if((temp_rc != RCL_RET_OK)){}}

void error_loop(){
  while(1){ digitalWrite(LED_PIN, !digitalRead(LED_PIN)); delay(100); }
}

void setup() {
    
  set_microros_transports();  
  allocator = rcl_get_default_allocator();
   
  //create init_options
  RCCHECK(rclc_support_init(&support, 0, NULL, &allocator));  

  // create node 
  RCCHECK(rclc_node_init_default(&node, "joint_state_publisher", "", &support));
  
  // create publisher
  RCCHECK(rclc_publisher_init_default(
    &publisher,
    &node,
    ROSIDL_GET_MSG_TYPE_SUPPORT(sensor_msgs, msg, JointState),
    "joint_states"));

  //sprintf(frame_id, "odom");
  static rosidl_runtime_c__String joint_name[5];
  static double positions[5];
  
  // Joint1  
  joint_name[0].data = "joint1";
  joint_name[0].size = 6;
  joint_name[0].capacity = 7;

  // Joint2
  joint_name[1].data = "joint2";
  joint_name[1].size = 6;
  joint_name[1].capacity = 7;

  // Joint3
  joint_name[2].data = "joint3";
  joint_name[2].size = 6;
  joint_name[2].capacity = 7;

  // Joint4
  joint_name[3].data = "joint4";
  joint_name[3].size = 6;
  joint_name[3].capacity = 7;

  // Joint5
  joint_name[4].data = "joint5";
  joint_name[4].size = 6;
  joint_name[4].capacity = 7;

  jstate.name.data = joint_name;
  jstate.name.size = 5;
  jstate.name.capacity = 5;
  jstate.position.data = positions;
  jstate.position.size = 5;
  jstate.position.capacity = 5;
}

void loop() {
  static float position0 = 0.0;
  static bool increment0 = true;
  
  uint32_t current = micros();
  jstate.header.stamp.sec = current / 1000000;
  jstate.header.stamp.nanosec = current - jstate.header.stamp.sec*1000000;
  if (increment0) {
    position0 += 0.1;
    if (position0 >=  1.50) increment0 = false;
  } else {
    position0 -= 0.1;
    if (position0 <= -1.50) increment0 = true;
  }

  jstate.position.data[0] = position0;
  // publish topic
  RCSOFTCHECK(rcl_publish(&publisher, &jstate, NULL));
  delay(1000);
}
```

joint_state の配列を5つ確保しているのは、トピックに関節数分の情報を与えておかないと、rviz2 が URDF のモデルを正しく構築できないためです。

このプログラムでは Joint1 を1秒毎に 0.1[rad]（5.7度）回転させ±86度の間を行ったり来たりするプログラムになります。トルクや速度は特に指定をしなくても問題なく動きました。（恐らく指定したほうがよいと思いますが、、、）

# display.launch.py の書き換え
SPRESENSE との接続実験をする前に、display.launch.py の書き換えが必要です。デフォルトのままでは、"Joint State Publisher" が立ち上がってしまい、"joint_state" トピックを専有してしまうため、SPRESENSE が出力する "joint_state" トピックが無効になってしまいます。

"Joint State Publisher" アプリを止めるのはさほど難しくありません。 ~/ros2_ws/src/urdf_tutorial/launch/display.launch.py の57行,58行をコメントアウトするだけです。

```python:~/rosn2_ws/src/urdf_tutorial/launch/display.launch.py
 53     return LaunchDescription([
 54         gui_arg,
 55         model_arg,
 56         rviz_arg,
 57         # joint_state_publisher_node,
 58         # joint_state_publisher_gui_node,
 59         robot_state_publisher_node,
 60         rviz_node
 61     ])
```

編集したら、今一度 colcon でビルドし直します。

```console
~/ros2_ws$ colcon build --packages-select urdf_tutorial
~/ros2_ws$ source install/setup.bash
```

これで rviz2 側の準備は完了です。

# URDF のモデルを動かしてみる
いよいよ URDF のモデルを動かします。上記スケッチを書き込んだSPRESENSEをUSBを接続したまま、micro-ROS-agent をシリアル接続で立ち上げます。

```console
~$ sudo docker run -it --rm -v /dev:/dev --privileged --net=host microros/micro-ros-agent:humble  serial　--dev /dev/ttyUSB0
[1659402842.881086] info     | TermiosAgentLinux.cpp | init                     | running...             | fd: 3
[1659402842.881813] info     | Root.cpp           | set_verbose_level        | logger setup           | verbose_level: 4

```

SPRESENSE に、先程の Arduino のスケッチを書き込みます。しばらくすると、micro-ROS-agent を立ち上げた コンソールにメッセージが追加して出てきます。これで、SPRESENSE とホストPCとの接続は完了です。

```console
~$ sudo docker run -it --rm -v /dev:/dev --privileged --net=host microros/micro-ros-agent:humble  serial　--dev /dev/ttyUSB0
[1659402842.881086] info     | TermiosAgentLinux.cpp | init                     | running...             | fd: 3
[1659402842.881813] info     | Root.cpp           | set_verbose_level        | logger setup           | verbose_level: 4
[1659348850.359973] info     | Root.cpp           | create_client            | create                 | client_key: 0x28E804DA, session_id: 0x81[1659348850.360103] info     | SessionManager.hpp | establish_session        | session established    | client_key: 0x28E804DA, address: 0
[1659348850.390805] info     | ProxyClient.cpp    | create_participant       | participant created    | client_key: 0x28E804DA, participant_id: 0x000(1)
[1659348850.407067] info     | ProxyClient.cpp    | create_topic             | topic created          | client_key: 0x28E804DA, topic_id: 0x000(2), participant_id: 0x000(1)
[1659348850.416714] info     | ProxyClient.cpp    | create_publisher         | publisher created      | client_key: 0x28E804DA, publisher_id: 0x000(3), participant_id: 0x000(1)
[1659348850.428346] info     | ProxyClient.cpp    | create_datawriter        | datawriter created     | client_key: 0x28E804DA, datawriter_id: 0x000(5), publisher_id: 0x000(3)
```

それが出来たら、別のコンソールを立ち上げて、URDF モデルを rviz2 で立ち上げます。

```console
~/ros2_ws$  ros2 launch urdf_tutorial display.launch.py model:=./src/urdf_tutorial/urdf/10-arm.urdf
```

すると画面が現れて、ロボットが回転しはじめます。左ペインにあるTFのチェックボックスを外すと回転している様子がよく分かると思います。

<iframe width="560" height="315" src="https://www.youtube.com/embed/lLiNOD7IApA" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

他の関節（Joint）も同じような要領でコマンドを送れば、変更をすることができます。トルクや速度を指定してみるのも面白いでしょう。ぜひやってみてください。

# 作ってみた感想
一番つまったのは、Joint State Publisher を立ち上げると、SPRESENSE のトピックが無効になることでした。先に SPRESENSE を立ち上げているので、同じトピック名なら SPRESENSE のほうが優先されるのだろうと思ったのですが甘かった。トピック名が重なった場合の優先順位が ROS2 の中でどうなっているのかは、まだ謎です。当然のことですが、トピック名は被らないようにしないといけないですね。その他の部分はすんなりと行ったのですが、URDF の理解が浅いので苦労します。メカ屋ではないため、組み上がりのイメージができなかったり、物理シミュレーションのための inertia などは、どう設定してよいのか分からなかったり。まだまだ、ROS2 の冒険は続きそうです。