ROS1を使ってみる その2(PythonのコードでPublishとSubscribe)
はじめに
今回はPythonのコードでROSのPublishとSubscribeを実行してみました。
これまでOpen Manipulatorを操作するのにROSを使っていたのですが、そのためのソフトウェアは用意されていたので、コマンドを実行するだけで使えるようになっていました。
これから自分で設計したロボット用のプログラムを作成するために、まずは簡単なプログラムから試してみました。
▼今回もWSL2のUbuntu 20.04で実行しています。
▼以前の記事はこちら
参考資料
書籍や公式のチュートリアルを参考にしました。
▼今回は特にこちらの書籍を参考にしています。Pythonのコードに関する記述が多い印象でした。
ただし書籍ではUbuntu 18.04を利用しているのですが、私はWSL2のUbuntu 20.04なのでそのまま実行できないこともありました。エラーへの対策は後述しています。
▼ROSの概念やコマンド、センサーや実機の利用などはこちらの書籍が詳しかったです。
今回の簡単なPublishとSubscribeのコードは、ROSのTutorialにもあります。
▼こちらのページです。
https://wiki.ros.org/ja/ROS/Tutorials/WritingPublisherSubscriber%28python%29
▼コードの実行についてはこちら
https://wiki.ros.org/ja/ROS/Tutorials/ExaminingPublisherSubscriber
Pythonのプログラムを作成する
ワークスペースとパッケージの作成
環境を構築するときはcatkin_wsというフォルダをよく作成するのですが、今回は本を読みながらだったのでbook_wsというフォルダを作成しました。
以下のコマンドでワークスペースを作成しました。
mkdir -p ~/book_ws/src
cd book_ws/src
catkin_init_workspace
cd ~/book_ws
catkin_make
▼ここではcatkin_init_workspace
コマンドを使いましたが、こちらの記事によると古いコマンドだそうです。catkin init
というコマンドがあるようです。
構築した環境を利用するため、以下のコマンドを実行します。
▼このコマンドはターミナルを開くたびに必要なので、面倒であれば~/.bashrcに追記してください。
source ~/book_ws/devel/setup.bash
以下のコマンドで新しくパッケージを作成します。
cd ~/book_ws/src
catkin_create_pkg ros_start rospy roscpp std_msgs
cd ~/book_ws
catkin_make
source ~/book_ws/devel/setup.bash
▼パッケージの作成に関するページはこちら
https://wiki.ros.org/ja/ROS/Tutorials/CreatingPackage
これからプログラムを追加するscriptsフォルダを作成しておきます。
cd ~/book_ws/src/ros_start/
mkdir scripts
cd scripts
プログラムの作成
書籍だと1行目が#!/usr/bin/env pythonだったのですが、そのままでは実行できませんでした。
今回は/usr/bin/にpython3.8があったので、そのパスを指定してみると実行できるようになりました。UbuntuのデフォルトのPythonのバージョンの違いなどが影響しているのかと思います。
▼lsコマンドで/usr/bin/を確認すると、python3.8がありました。
Publishするためのtalker.py、Subscribeするためのlistener.pyを用意しました。sudo nano ファイル名
で、プログラムを記述しました。
▼talker.pyはこちら
#!/usr/bin/python3.8
import rospy
from std_msgs.msg import String
rospy.init_node('talker')
pub = rospy.Publisher('chatter', String, queue_size=10)
rate = rospy.Rate(10)
while not rospy.is_shutdown():
hello_str = String()
hello_str.data = "hello world %s" % rospy.get_time()
pub.publish(hello_str)
rate.sleep()
▼listener.pyはこちら
#!/usr/bin/python3.8
import rospy
from std_msgs.msg import String
def callback(message):
rospy.loginfo("I heard %s", message.data)
rospy.init_node('listener')
sub = rospy.Subscriber('chatter', String, callback)
rospy.spin()
ファイルを作成後、このままrosrunで実行しようとしても実行できませんでした。
▼they're either not files, or not executableと表示されています。
以下のコマンドで実行できるようにします。
sudo chmod 755 talker.py listener.py
▼sudoをつけないと、エラーが出ていました。
Permission deniedや、not permittedと表示されたときは、コマンドの前にsudoをつけると実行できることがあります。sudoはsuperuser doの略で、root権限でコマンドを実行することができます。
ここまでの作業で先程のプログラムを実行できるようになったので、実行してみました。
作業していたターミナルで、roscoreを実行します。
roscore
新しく2つのターミナルを開いて、それぞれコマンドを実行しました。
cd book_ws
source devel/setup.bash
roscd ros_start/scripts/
./talker.py
cd book_ws
source devel/setup.bash
roscd ros_start/scripts/
./listener.py
▼listener.pyで受信できていることが確認できました。
roslaunchで実行する
talker.pyとlistener.pyを実行するのに別々のターミナルを開いていましたが、一つのターミナルで実行するためにlaunchファイルを作成します。
sudo nano chat.launch
を実行し、以下の内容を記述しました。
<launch>
<node pkg="ros_start" name="talker" type="talker.py"/>
<node pkg="ros_start" name="listener" type="listener.py"/>
</launch>
作成したlaunchファイルを、以下のコマンドで実行しました。
roslaunch ros_start chat.launch
▼この状態で別のターミナルからrostopicを確認すると、/chatterにデータが送信されていることが確認できました。
▼rqt_graphを実行すると、ノードの関係が表示されていました。
roslaunchを実行したターミナルではlistener.pyで受信したものが表示されていませんでしたが、以下の2つの方法で表示されるようになりました。
1つ目は以下のコマンドのように、--screenという引数を渡す方法です。
roslaunch ros_start chat.launch --screen
2つ目はlaunchファイルにoutput="screen"を追加する方法です。
<launch>
<node pkg="ros_start" name="talker" type="talker.py"/>
<node pkg="ros_start" name="listener" type="listener.py" output="screen"/>
</launch>
▼以下のように、launchファイルを実行したときにlistener.pyの出力も表示されました。
Windowsでrospyを実行する
ROSの環境を構築しなくても、rospyがPython用に実装されているのなら実行できるのでは?と思い、調べていると先駆者の方の記事がありました。
▼純粋なPythonのパッケージとしてrospyをインストールできるように作成されています。
https://qiita.com/otamasan/items/7ac7732a5c3d47ec3028
▼GitHubのリポジトリはこちら
https://github.com/rospypi/simple
Ubuntu環境で実行すればいいのでWindowsで実行することは無いと思うのですが、せっかくなので試してみました。
記事に書かれていたrosmasterはWindows環境では実行できなかったのですが、WSL2のUbuntu 20.04で起動していたroscoreで通信できるようになっていました。
▼ちなみに、masterが無いとこの後のプログラムを実行したときにエラーが出ました。
ROS_MASTER_URIはWindowsの環境変数で設定したものが反映されるようで、http://localhost:11311で接続できました。
▼Ubuntu環境のIPアドレスを設定すると、逆に通信できませんでした。
今回はPythonを実行するにあたって、私の開発したNode-REDでPythonを実行できるpython-venvノードを利用しています。Pythonの仮想環境も作成されます。
▼Node-REDのパレットの管理からインストールできます。
https://flows.nodered.org/node/@background404/node-red-contrib-python-venv
▼全体のフローはこちら
[{"id":"7ebbb2b6fbd2c8ed","type":"venv","z":"6f967b24d95ab8d8","venvconfig":"4657b6fbdbaf6f7e","name":"","code":"import rospy\nfrom std_msgs.msg import String\n\nrospy.init_node('talker')\npub = rospy.Publisher('chatter', String, queue_size=10)\nrate = rospy.Rate(10)\nwhile not rospy.is_shutdown():\n hello_str = String()\n hello_str.data = \"hello world %s\" % rospy.get_time()\n pub.publish(hello_str)\n rate.sleep()","continuous":true,"x":510,"y":1140,"wires":[["ac214bf0988d86e8"]]},{"id":"24145892846b37dd","type":"pip","z":"6f967b24d95ab8d8","venvconfig":"4657b6fbdbaf6f7e","name":"","arg":"--extra-index-url https://rospypi.github.io/simple rospy-all","action":"install","tail":false,"x":510,"y":1100,"wires":[["0609b00d45943805"]]},{"id":"1885b0143107cc2f","type":"inject","z":"6f967b24d95ab8d8","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":360,"y":1060,"wires":[["24145892846b37dd","e89e3506a614225f"]]},{"id":"0609b00d45943805","type":"debug","z":"6f967b24d95ab8d8","name":"debug 129","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":670,"y":1100,"wires":[]},{"id":"57ebd31e7ee67192","type":"inject","z":"6f967b24d95ab8d8","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":360,"y":1140,"wires":[["7ebbb2b6fbd2c8ed"]]},{"id":"ac214bf0988d86e8","type":"debug","z":"6f967b24d95ab8d8","name":"debug 130","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":670,"y":1140,"wires":[]},{"id":"a74470c0a5853e48","type":"venv","z":"6f967b24d95ab8d8","venvconfig":"4657b6fbdbaf6f7e","name":"","code":"import rospy\nfrom std_msgs.msg import String\ndef callback(message):\n print(\"I heard %s\", message.data)\n\nrospy.init_node('listener')\nsub = rospy.Subscriber('chatter', String, callback)\nrospy.spin()","continuous":true,"x":510,"y":1200,"wires":[["9e2cbc12ed123990"]]},{"id":"d57aa0496618ef88","type":"inject","z":"6f967b24d95ab8d8","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":360,"y":1200,"wires":[["a74470c0a5853e48"]]},{"id":"9e2cbc12ed123990","type":"debug","z":"6f967b24d95ab8d8","name":"debug 131","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":670,"y":1200,"wires":[]},{"id":"e89e3506a614225f","type":"pip","z":"6f967b24d95ab8d8","venvconfig":"4657b6fbdbaf6f7e","name":"","arg":"--extra-index-url https://rospypi.github.io/simple rosmaster defusedxml","action":"install","tail":false,"x":510,"y":1060,"wires":[["206c0ebfc1f6c3ca"]]},{"id":"206c0ebfc1f6c3ca","type":"debug","z":"6f967b24d95ab8d8","name":"debug 132","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":670,"y":1060,"wires":[]},{"id":"4657b6fbdbaf6f7e","type":"venv-config","venvname":"pyenv","version":"default"}]
pipノードで必要なパッケージをインストールしています。
▼仮想環境の有効化などはノードで行われるので、引数だけ設定すればインストールできます。
--extra-index-url https://rospypi.github.io/simple rospy-all
▼以下のように設定できます。injectノードから実行すると、インストールできます。
先程のPythonoのプログラムをvenvノードに記述し、実行してみました。
▼Publisherのプログラムはこちら
▼Subscriberのプログラムはこちら
▼実行すると、以下のような警告が表示されましたが実行できました。
どうやらlog機能は使えないようです。listener.pyに含まれていた、rospy.loginfo()は使えませんでした。
先程の記事で紹介されていたサンプルコードではprint()を使っていたのですが、print()に変更すると表示されました。
▼受信はできているのですが、これまでと違って%sが残っています。
▼%sが残るのはノードで実行している影響では無くて、コマンドプロンプトで実行しても同様でした。print()とrospy.loginfo()の仕様の違いなのかもしれません。
Python同士では通信できていたのですが、ROS側では受信できていないようでした。
▼roscoreを実行していたUbuntu環境でrostopicを確認すると、listには/chatterがあったのですが受信内容は表示されませんでした。rqt_graphでも表示されていませんでした。
roscoreを終了してもrospyの通信は継続していたので、Python同士で直接やり取りをしているかのようでした。公式のやり方では無いのでサポートされていない部分もあるとは思いますが、面白い取り組みだと思いました。
最後に
Windowsで実行したrospyだとUbuntuで実行したときのようにはなっていないようでしたが、Windowsで実行すること自体が特にないとは思います。今後はROSの環境を構築したUbuntuで実行することになりそうです。
今回はPublishとSubscribeだけのシンプルなプログラムでしたが、他のPythonのパッケージと組み合わせるとどう利用できるかが気になっています。WhisperやVOICEVOXも利用できるのでしょうか。いろいろ試してみようと思います。
▼前回の記事でYOLOの物体検出をサンプルだけ試していたのですが、Pythonでも実行できました。検出結果をPublishしたいなと考えています。