ROS覚書
背景
ROSを使ってロボット開発をしていると似たようなものを繰り返す使う羽目になるのでここにまとめる。 現在ROS1とROS2があるが、ここではROS1に限定して述べる。
ROS概要
ROSとはROSはRobot OSの略。ただし実際にはOSではなくコンピューターとハードウェアをつなぐミドルウェアのようなもの。ROSを使うことでロボットのハードウェアを抽象化でき、パートごとの開発・アップデートが容易になる。
ROSを使う理由
多くのロボット開発でROSが使用されている。その理由として以下があげられる
- 完全無料
- 市販のセンサーがROSに対応済
- 分散型システムの構築が容易
- 便利機能が色々
完全無料
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を始めるにあたっては以下順次行う必要がある。
- ROSのインストール
- Workspaceの作成
- Packageの作成
- Nodeの作成
それぞれの手順・コマンドを以下に記載する。
ROSのインストール
ここではROSのmelodicをインストールする前提で説明を行う。 ROSのインストールは本家の説明の通りに実行すればよい。 wiki.ros.org 推奨設定としてコマンドを以下に記載する。
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システムを起動する手順を以下に示す。
- roscoreコマンド
- 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の場合
Actionの場合
自作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で開発する方法を下記に記載した。
Linuxのコマンド
Pythonスクリプトの実行やUSBデバイスの使用時にエラーが発生した場合の対処法を記載する。
Pythonスクリプトを実行する場合
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>