📑

ROS覚書

2022/03/16に公開

背景

ROSを使ってロボット開発をしていると似たようなものを繰り返す使う羽目になるのでここにまとめる。
現在ROS1とROS2があるが、ここではROS1に限定して述べる。
*本記事ははてなブログにも投稿済み。パクリじゃないよ。

ROS概要

ROSとはROSはRobot OSの略。ただし実際にはOSではなくコンピューターとハードウェアをつなぐミドルウェアのようなもの。ROSを使うことでロボットのハードウェアを抽象化でき、パートごとの開発・アップデートが容易になる。

ROSを使う理由

多くのロボット開発でROSが使用されている。その理由として以下があげられる

  1. 完全無料
  2. 市販のセンサーがROSに対応済
  3. 分散型システムの構築が容易
  4. 便利機能が色々

完全無料

ROSはオープンソースで完全無料で使用可能である。

市販のセンサーがROSに対応済

ロボットに搭載するLiDARセンサーやカメラ、IMUモジュール等の多くでROS用のPackageがリリースされており、ROSで開発されたロボットシステムで使用するには簡単なコマンドを打つだけというお手軽仕様である。

分散型システムの構築が容易

ROSを用いたシステムは、Nodeと呼ばれる小単位で構成される。Nodeを各ハードウェアの機能に対応させることで各ハードウェア毎の開発を完全に切り分けて行うことが可能である。

便利機能が色々

ROSにはロボットのセンサーデータを可視化するRVIZ、シミュレーションを行うGazebo、自己位置推定や自律移動が可能なAMCL/MoveBaseといったパッケージが準備されている。これらの既存の機能を活用することで市販のメインPCとセンサーとモーター等を購入し接続するだけで自己位置推定が可能な自律移動ロボットを簡単に開発できる。

ROSの構成

ROS開発は以下の構成で行われる。
Workspace > Package > Node
一つのマシンには一つのWorksapceがあり、一つのWorksapceに複数のPackageがあり、一つのPackageに複数のNodeがある。

ROSを始めるには

ROSを始めるにあたっては以下順次行う必要がある。

  1. ROSのインストール
  2. Workspaceの作成
  3. Packageの作成
  4. Nodeの作成

それぞれの手順・コマンドを以下に記載する。

ROSのインストール

ここではROSのmelodicをインストールする前提で説明を行う。
ROSのインストールは本家の説明の通りに実行すればよい。
[http://wiki.ros.org/melodic/Installation/Ubuntu:embed:cite]
推奨設定としてコマンドを以下に記載する。

sudo sh -c 'echo "deb http://packages.ros.org/ros/ubuntu $(lsb_release -sc) main" > /etc/apt/sources.list.d/ros-latest.list'
sudo apt install curl # if you haven't already installed curl
curl -s https://raw.githubusercontent.com/ros/rosdistro/master/ros.asc | sudo apt-key add -
sudo apt update
sudo apt install ros-melodic-desktop-full
echo "source /opt/ros/melodic/setup.bash" >> ~/.bashrc
source ~/.bashrc
sudo apt install python-rosdep python-rosinstall python-rosinstall-generator python-wstool build-essential
sudo apt install python-rosdep
sudo rosdep init
rosdep update

##Workspaceの作成
Worksapceは以下のコマンドで行う。

mkdir -p ~/catkin_ws/src
cd ~/catkin_ws/src
catkin_init_workspace
cd ~/catkin_ws
catkin_make

lsコマンドでcatkin_wsディレクトリ下にbuildディレクトリとdevelディレクトリが生成されていることが確認できる。

Packageの作成

Packageは以下のコマンドで作成する。
ここではPackageの名前をpkg_nameとした。

cd ~/catkin_ws/src
catkin_create_pkg pkg_name std_msgs roscpp rospy

lsコマンドでsrcディレクトリ下に以下にpackage_nameディレクトリが生成されていることが確認できる。

Nodeの作成

NodeはC++またはPythonで記述することになる。
Node作成の前にPackageの中身を確認してみる。

cd ~/catkin_ws/src/pkg_name

lsコマンドでCMakeLists.txt、package.xml、srcディレクトリ、includeディレクトリが生成されていることが確認できる。
NodeをC++で記述する場合はsrcディレクトリに移動しソースコードを作成する。
Pythonで記述する場合はscriptsディレクトリ内にスクリプトを作成する必要がある。デフォルトではscriptsディレクトリはないため新規にscriptsディレクトリを作成する必要がある。

cd ~/catkin_ws/src/pkg_name
mkdir scripts

早速Nodeを作成したいところだが、はやる気持ちを抑えてもう少しNodeについて理解深めよう。

Node間の通信3種類

ROSシステムは多くの小単位Nodeで構成されており、各Node間でデータがやり取りされてシステム全体として機能している。
Node間のデータのやり取りの方法は3種類ある。Topic、 Service、Actionである。

Topic

Publisherは任意のTopicに任意のMessageを発行する。Subscriberは任意のTopicにMessageが発行されたことを受けて任意の処理を実行する。一般的にはPublisherのMessage発行タイミングは周期的に行われる。

Service

Service ClientがService ServerにRequestを送信する。Service ServerはRequestを受けて任意の処理を行いResponseを返送する。

Action

Action ClientがAction ServerにGoalを送信する。ServerはGoalを受けて任意の処理を行い、その過程で随時FeedbackをActionClientに返送する。一連の処理が完了するとAction ServerがActionClientにResultを送信する。

Nodeの動きを把握する

Nodeの動きを把握するために実際にROSを動かしてみよう。

ROSシステムを起動する手順を以下に示す。

  1. roscoreコマンド
  2. rosrunコマンドで各パッケージの各ノードを起動

亀を動かす

ROSのチュートリアルとして用意されている亀を動かしてみよう。
ターミナルを立ち上げて以下コマンドを実行する。

roscore

別のターミナルを立ち上げて以下コマンドを実行しturtlesimパッケージのturtlesim_node ノードを起動する。

rosrun turtlesim turtlesim_node

画面上に亀が表示される。

次にさらに別のターミナルを立ち上げて以下コマンドを実行しturtlesimパッケージのturtle_teleop_key ノードを起動する。

rosrun turtlesim turtle_teleop_key

これでキーボード操作で亀が移動するようになる。
亀が動いている裏で、

  • turtle_teleop_keyノードがキーボードの入力情報を特定のtopicにpublishする
  • turtlesim_nodeノードが特定のtopicをsubscribeする
    が行われている。実際にデバッグ機能を用いて各Node間のつながりを確認してみる。
    ターミナルをさらに立ち上げて以下のコマンドを実行するとNode間のつながりが確認できる。
rosrun rqt_graph rqt_graph

以下が表示される。楕円に囲まれた文字がNode名であり、楕円をつないだ線上の文字がTopic名である。

ソースコードの例

Nodeについて多少理解が深まったと思われるのでNodeやTopic/Service/Actionを指定するソースコードを以下に例示する。

Topicの場合

記述方法は様々だが、ここではClassを使用して一つのコードにPublisherとSubscriberを記述した例を記載する。

C++

Node名"talker"、pub_topicにpublish、sub_topicをsubscribeする場合のコード例は以下の通り。

#include "ros/ros.h"
#include <std_msgs/String.h>
class Talker{
private:
    ros::NodeHandle nh;
    ros::Publisher pub = nh.advertise<std_msgs::String>("/pub_topic", 1);;
    ros::Subscriber sub = nh.subscribe("/sub_topic", 1,  &Talker::callback, this);
    std_msgs::String pub_msg;
    
public:
    NodeTestClass(){
        ros::Rate loop_rate(1);
        while(ros::ok()){
            publication();
            ros::spinOnce();
            loop_rate.sleep(); 
        }
    }
    void publication(){
        pub_msg.data = "Hello world";
        pub.publish(pub_msg);
    }
    
    void callback(const std_msgs::String::ConstPtr& msg){
        std::cout << msg->data.c_str() << std::endl;
    }    
};
int main(int argc, char**argv){
    ros::init(argc, argv, "talker");
    Talker talker;
}

Python

#!/usr/bin/python
import rospy
from std_msgs.msg import String

class Talker():
    def __init__(self)
        rospy.init_node('talker')
        self.pub = rospy.Publisher('pub_topic', String, queue_size=1)
        self.sub = rospy.Subscriber('sub_topic', String, callback)
    loop_rate = rospy.Rate(1)

    while not rospy.is_shutdown():
        self.publication()
        loop_rate.sleep()

    def publication(self):
        msg = 'Hello World'
        self.pub.publish(msg)

    def callback(self, msg):
        rospy.info(msg)

if __name__ == '__main__':
    talker = Talker()

Serviceの場合

TBD

Actionの場合

TBD

自作Nodeを起動するには

C++でNodeを自作した場合は起動前にビルドする必要がある。
ビルドの下準備としてPackage直下のCMakeLists.txtに作成したソースコードのファイル名やNode名などを記載する必要がある。
例えば、ファイル名 talker_class.cppで記述したNode名"talker"を起動したい場合は、Package直下のCMakeLists.txtの以下add_executableとtarget_link_librariesを下記のように記述する。

add_executable(talker src/talker_class.cpp) 
target_link_libraries( talker ${catkin_LIBRARIES} )

CMakeLists.txtを修正したら、catkin_wsディレクトリ下でコマンドcatkin_makeを実行する。

cd ~/catkin_ws
catkin_make

エラーが表示されなければビルドは成功である。

Messageの型を自作する

上記ソースコード例では既存のMessageの型であるString型を使用している。デフォルトでいくつかの型は準備されている。Topicの場合は下記URLを参照。
[http://wiki.ros.org/std_msgs]

Messageの型は自作することも可能である。自作する場合は、Packageディレクトリ直下にmsgディレクトリを作成し、msgディレクトリ内にname.msgというファイルを作成する。
例えば、人の名前(String)と年齢(Uint8)と性別(Bool, maleがtrueかfalseか)をひとまとめにしたMessageを取り扱う場合は下記のファイルを作成する。

String name
Uint8 age
Bool male

ファイル名はPerson.msgとでもしておき、msgディレクトリに保存しておく。
自作したMessageを使用するはCMakeLists.txtに登録する必要がある。

find_package(catkin REQUIRED COMPONENTS
    roscpp
    rospy
    std_msgs
    message_generation
)

add_message_files(
    FILES
    Person.msg
 )

generate_messages(
    DEPENDENCIES
    std_msgs
)

Serviceの型を自作する

Message同様Serviceの型も自作可能である。Service方はPackage直下にsrvディレクトリを作成し、ディレクトリ内にname.srvファイルを作成する。
Serviceはrequestとresponseが必要であることから下記のような記述内容となる。

String name
---
Uint8 age
Bool male

'---'でrequestとresponseを分けている。上記の例ではServie Clientがnameでrequestし、Service Serverがageとmale(性別、trueなら男)を返す記述例である。
Serviceファイルを新規作成した際もPackage直下のCMakeLists.txtを修正する必要がある。

find_package(catkin REQUIRED COMPONENTS
    roscpp
    rospy
    std_msgs
    message_generation
)

add_service_files(
    Person.srv
)

generate_messages(
    DEPENDENCIES
    std_msgs
)

Actionの型を自作する

MessageとService同様Actionの型も自作可能である。ActionはPackage直下にactionディレクトリを作成し、ディレクトリ内にname.actionファイルを作成する。
Serviceはrequestとresponseが必要であることから下記のような記述内容となる。

String meal
---
Bool done
String comment
---
UInt8 progress

'---'でgoalとresultとfeedbackを分けている。上記の例ではAction Clientがmeal(どの食事を作るか)でgoalを示しAction Serverが途中経過をprogress(現在何%出来上がった)で返送しつつ、最終的にdone(完成した)とcomment(メモ)をresultとして送る記述例である。
Actionファイルを新規作成した際もPackage直下のCMakeLists.txtを修正する必要がある。

find_package(catkin REQUIRED COMPONENTS
    roscpp
    rospy
    std_msgs
    message_generation
)

add_action_files(
    Meal.action
)

generate_messages(
    DEPENDENCIES
    std_msgs
    actionlib_msgs
)

デバッグ機能いろいろ

ROSを活用する上で便利なデバッグ機能を以下に記載する。

rqt_graph

ターミナルで以下コマンドを実行すると、Nodeとその通信の一覧が図示される。

rosrun rqt_graph rqt_graph

Topicなどを一覧表示

ターミナルで以下コマンドを実行すると、Topic一覧が表示される。

rostopic list

Node一覧を見る場合は以下のコマンドを打つ。

rosnode list

ターミナルからpublish

作成したNodeの動作確認目的などで特定のmessageをpublishしたい場合がある。その都度デバッグ用のNodeを作成する必要はなくrotopic pub コマンドでmessageをpublishできる。
例えばTopic名"/topic"にString型の"Hello World"をpublishしたい場合は以下のコマンドを実行すればよい。

rostopic pub -1 /topic std_msgs/String "Hello World"

ターミナルでsubscribe

特定のTopicのmessageをsubscribeしたい場合もわざわざ専用のNodeを作成する必要はない。
例えばTopic名"/topic"のmessageを確認したい場合は以下のコマンドを打てばよい。

rostopic echo /topic

RVIZにデータを表示

自作したセンサ基板で取得したデータをRVIZに表示する方法を記載する。

下記のMaker型のmarker_dataを作成しMarker型で任意のTopicにpublishするだけでRVIZ上でMarkerが表示可能となる。

marker_data = Marker()
marker_data.header.frame_id = "map"
marker_data.header.stamp = rospy.Time.now()
marker_data.type = marker_data.SPHERE
marker_data.action = Marker.ADD

marker_data.pose.position.x = 0.0
marker_data.pose.position.y = 0.0
marker_data.pose.position.z = 0.0

marker_data.pose.orientation.x = 0.0
marker_data.pose.orientation.y = 0.0
marker_data.pose.orientation.z = 0.0
marker_data.pose.orientation.w = 0.0

marker_data.color.r = 1.0
marker_data.color.g = 1.0
marker_data.color.b = 1.0
marker_data.color.a = 1.0

marker_data.scale.x = 0.05
marker_data.scale.y = 0.05
marker_data.scale.z = 0.05

marker_data.lifetime = rospy.Duration()

marker_data.typeはRVIZ上に表示するMarkerの形状を決めるもので、球体以外にも矢印(ARROW)や立方体(CUBE)などがある。
marker_data.pose.position.x/y/zはMarkerが表示される位置を示すもので、データに応じて変更すればよい。
marker_data.pose.orientation.x/y/z/wはMarkerの向きを示すもので、球体の場合は不要、矢印の場合は適切な方向を定める必要がある。
marker_data.color.r /g/b/aはMarkerの色を決めるものである。いずれもデフォルトでは0(黒色、透明)なため適当な数値を設定しておく。
marker_data.scale.x/y/zはMarkerのサイズを決めるものである。

pyqt5でGUIツールを作成

デバッグ効率化のためにGUIツールをpyqt5で開発する方法を下記に記載した。
https://zenn.dev/yoshima22/articles/00f28f130b6337

Linuxのコマンド

Pythonスクリプトの実行やUSBデバイスの使用時にエラーが発生した場合の対処法を記載する。

Pythonスクリプトを実行する場合

Pythonスクリプト名がscript.pyの場合

sudo chmod +x script.py

USBデバイスを使用する場合

USBデバイスのCOMポートが/dev/ttyUSB0の場合

sudo chmod 666 /dev/ttyUSB0

Windows11でROS開発

WindowsでもWSLを用いる事で容易にROSの開発環境を構築できるようになった。特にWindoows11ではGUIのために必要であったVcXsrvのインストールが不要となった。ここではWindows11でROS開発環境を構築する方法を記載する。

Ubuntuのインストール

MS StoreからUbuntu18.04をダウンロード・インストールする。

ROSとGUI環境のインストール

検索ボックスにWSLと入力しWSL(Ubuntu)を実行する。
立ち上がったUbuntuで以下コマンドを実行する。

sudo apt install x11-apps -y

完了後に以下コマンドを実行し別windowに目が表示されればGUI環境の構築は完了。

xeyes

ROSのインストールは本記事の" ROSのインストール"を参照。

USBデバイスの認識

USBデバイスをUbuntu側で使用する方法を記載する。
Ubuntu側で以下を実行する。

sudo apt install linux-tools-5.4.0-77-generic hwdata
sudo update-alternatives --install /usr/local/bin/usbip usbip /usr/lib/linux-tools/5.4.0-77-generic/usbip 20

WindowsPCにUSBデバイスを接続した状態でpoweshell管理者モードで以下コマンドを実行し接続したUSBデバイス情報(busid)を把握する。

usbipd wsl list

上記で取得したbusidを用いて下記コマンドを実行する。

usbipd wsl attach --busid <busid>

これでUbuntu側にUSBデバイスが認識されるようになる。

Discussion