#◆ 前回記事
RaspberryPi3とZumoとROSで半永久自走式充放電ロボを作成したい_003日目_独自Message・Service の続き
#◆ はじめに
前回は Raspberry Pi のROS環境へ、 「独自Message」「独自Service」 を実装したため、今回は 「独自のAction」 の実装を試行したい。
#◆ Actionとは
「ロボットに対して自律移動の命令を送り、移動が終わったときに結果が失敗したか成功したかを即座に知りたい。」というようなシチュエーションに対応するための通信方式、らしい。
「Service」形式 では、呼び出し先(子側)の処理が終わるまで待つため、上記の用途として適切ではない。
「TOPIC」形式 では、単純な実装では相手側の処理結果を知る仕組みが無いため、結果を返すためだけの「TOPIC」を追加で作成する必要があり、あまりスマートではない。
##* 基本用語
-
Action
- 処理に時間がかかる場合、実行中に要求を取り消したり、要求の進行状況を定期的に調べることができる
- 「Service」形式 に非同期通信機能を付加した便利方式のイメージ
- 非同期通信方式
-
Action Message
- Action通信方式で使用する伝達対象となるデータ
- TOPIC の Message に該当
-
Goal
- 日本語で "ゴール" Serverを呼び出す際の引数
- Action Message に定義する項目の一つ
- 初期化パラメータのようなもの
-
Result
- 日本語で "結果" Server側処理の結果
- Action Message に定義する項目の一つ
- 処理が終わった時に1回だけ通知される
-
Feedback
- 日本語で "反応" Server側処理の途中経過
- Action Message に定義する項目の一つ
- Resultとは異なり複数回通知される
-
Action Server
- 「Service」形式 の 「Server」 に該当 (呼び方が違うだけ)
-
Action Client
- 「Service」形式 の 「Client」 に該当 (呼び方が違うだけ)
- Serviceを呼び出すときに内部的には、Message を TOPIC へ Publish しているだけらしい
-
Actionlib
- 「Action」を実装するときに Import する必要のあるライブラリ
-
フィボナッチの数列(Fibonacci numbers)
- 算数レベルの足し算を小難しい表現にしただけ
- 最初の2つが "1" で、3つ目以降の数字はすべて直前の2つの数字の和になっている数列
- 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, ... ということ
- 1+1=2, 1+2=3, 2+3=5, 3+5=8, ...
##* Actionの概念図
自分なりに腹落ちさせたイメージは下図のとおり。
#◆ Actionのサンプル実装
今回は、 Action Server側 で フィボナッチ数列 を計算しつつ、計算の途中経過を Action Client側 へ Feedback する仕組みを構築してみることにする。
といっても、チュートリアル段階ではコーディングを極力せずにサンプルプログラムに頼れる部分は最大限頼ってしまう。
##* Actionファイルのサンプルダウンロード
$ cd ~/catkin_ws
$ source devel/setup.bash
$ roscd tutorials
$ mkdir action;cd action
$ wget -O fibonacci.action https://raw.githubusercontent.com/ros/common_tutorials/indigo-devel/actionlib_tutorials/action/Fibonacci.action
**fibonacci.action の内容**
#goal definition
int32 order
---
#result definition
int32[] sequence
---
#feedback
int32[] sequence
##* ActionServer / ActionClient のサンプルダウンロード
$ roscd tutorials/scripts
$ wget https://raw.githubusercontent.com/ros/common_tutorials/indigo-devel/actionlib_tutorials/scripts/fibonacci_client.py
$ wget https://raw.githubusercontent.com/ros/common_tutorials/indigo-devel/actionlib_tutorials/scripts/fibonacci_server.py
Action Server を呼び出し、フィボナッチ数列の生成を指示するプログラム「fibonacci_client.py」。
Action Server を呼び出すときに Goal のパラメータを order=20
とする。
20個分のフィボナッチ数列を生成するよう Server側 へ指示する。
なお、ダウンロードしたサンプルプログラムは自分が作成している環境のパッケージ名とは異なるパッケージ名を指定しているため、パッケージ名部分を自分の環境に合わせて修正する。
**編集前のfibonacci_client.py の内容**
#! /usr/bin/env python
from __future__ import print_function
import rospy
# Brings in the SimpleActionClient
import actionlib
# Brings in the messages used by the fibonacci action, including the
# goal message and the result message.
import actionlib_tutorials.msg
def fibonacci_client():
# Creates the SimpleActionClient, passing the type of the action
# (FibonacciAction) to the constructor.
client = actionlib.SimpleActionClient('fibonacci', actionlib_tutorials.msg.FibonacciAction)
# Waits until the action server has started up and started
# listening for goals.
client.wait_for_server()
# Creates a goal to send to the action server.
goal = actionlib_tutorials.msg.FibonacciGoal(order=20)
# Sends the goal to the action server.
client.send_goal(goal)
# Waits for the server to finish performing the action.
client.wait_for_result()
# Prints out the result of executing the action
return client.get_result() # A FibonacciResult
if __name__ == '__main__':
try:
# Initializes a rospy node so that the SimpleActionClient can
# publish and subscribe over ROS.
rospy.init_node('fibonacci_client_py')
result = fibonacci_client()
print("Result:", ', '.join([str(n) for n in result.sequence]))
except rospy.ROSInterruptException:
print("program interrupted before completion", file=sys.stderr)
**編集後のfibonacci_client.py の内容**
#! /usr/bin/env python
from __future__ import print_function
import rospy
# Brings in the SimpleActionClient
import actionlib
# Brings in the messages used by the fibonacci action, including the
# goal message and the result message.
import tutorials.msg
def fibonacci_client():
# Creates the SimpleActionClient, passing the type of the action
# (FibonacciAction) to the constructor.
client = actionlib.SimpleActionClient('fibonacci', tutorials.msg.fibonacciAction)
# Waits until the action server has started up and started
# listening for goals.
client.wait_for_server()
# Creates a goal to send to the action server.
goal = tutorials.msg.fibonacciGoal(order=20)
# Sends the goal to the action server.
client.send_goal(goal)
# Waits for the server to finish performing the action.
client.wait_for_result()
# Prints out the result of executing the action
return client.get_result() # A FibonacciResult
if __name__ == '__main__':
try:
# Initializes a rospy node so that the SimpleActionClient can
# publish and subscribe over ROS.
rospy.init_node('fibonacci_client_py')
result = fibonacci_client()
s1 = 'Result:'
s2 = ', '.join([str(n) for n in result.sequence])
print("Result:", ', '.join([str(n) for n in result.sequence]))
except rospy.ROSInterruptException:
print('program interrupted before completion', file=sys.stderr)
**編集前のfibonacci_server.py の内容**
#! /usr/bin/env python
import rospy
import actionlib
import actionlib_tutorials.msg
class FibonacciAction(object):
# create messages that are used to publish feedback/result
_feedback = actionlib_tutorials.msg.FibonacciFeedback()
_result = actionlib_tutorials.msg.FibonacciResult()
def __init__(self, name):
self._action_name = name
self._as = actionlib.SimpleActionServer(self._action_name, actionlib_tutorials.msg.FibonacciAction, execute_cb=self.execute_cb, auto_start = False)
self._as.start()
def execute_cb(self, goal):
# helper variables
r = rospy.Rate(1)
success = True
# append the seeds for the fibonacci sequence
self._feedback.sequence = []
self._feedback.sequence.append(0)
self._feedback.sequence.append(1)
# publish info to the console for the user
rospy.loginfo('%s: Executing, creating fibonacci sequence of order %i with seeds %i, %i' % (self._action_name, goal.order, self._feedback.sequence[0], self._feedback.sequence[1]))
# start executing the action
for i in range(1, goal.order):
# check that preempt has not been requested by the client
if self._as.is_preempt_requested():
rospy.loginfo('%s: Preempted' % self._action_name)
self._as.set_preempted()
success = False
break
self._feedback.sequence.append(self._feedback.sequence[i] + self._feedback.sequence[i-1])
# publish the feedback
self._as.publish_feedback(self._feedback)
# this step is not necessary, the sequence is computed at 1 Hz for demonstration purposes
r.sleep()
if success:
self._result.sequence = self._feedback.sequence
rospy.loginfo('%s: Succeeded' % self._action_name)
self._as.set_succeeded(self._result)
if __name__ == '__main__':
rospy.init_node('fibonacci')
server = FibonacciAction(rospy.get_name())
rospy.spin()
**編集後のfibonacci_server.py の内容**
#! /usr/bin/env python
import rospy
import actionlib
import tutorials.msg
class fibonacciAction(object):
# create messages that are used to publish feedback/result
_feedback = tutorials.msg.fibonacciFeedback()
_result = tutorials.msg.fibonacciResult()
def __init__(self, name):
self._action_name = name
self._as = actionlib.SimpleActionServer(self._action_name, tutorials.msg.fibonacciAction, execute_cb=self.execute_cb, auto_start = False)
self._as.start()
def execute_cb(self, goal):
# helper variables
r = rospy.Rate(1)
success = True
# append the seeds for the fibonacci sequence
self._feedback.sequence = []
self._feedback.sequence.append(0)
self._feedback.sequence.append(1)
# publish info to the console for the user
rospy.loginfo('%s: Executing, creating fibonacci sequence of order %i with seeds %i, %i' % (self._action_name, goal.order, self._feedback.sequence[0], self._feedback.sequence[1]))
# start executing the action
for i in range(1, goal.order):
# check that preempt has not been requested by the client
if self._as.is_preempt_requested():
rospy.loginfo('%s: Preempted' % self._action_name)
self._as.set_preempted()
success = False
break
self._feedback.sequence.append(self._feedback.sequence[i] + self._feedback.sequence[i-1])
# publish the feedback
self._as.publish_feedback(self._feedback)
# this step is not necessary, the sequence is computed at 1 Hz for demonstration purposes
r.sleep()
if success:
self._result.sequence = self._feedback.sequence
rospy.loginfo('%s: Succeeded' % self._action_name)
self._as.set_succeeded(self._result)
if __name__ == '__main__':
rospy.init_node('fibonacci')
server = fibonacciAction(rospy.get_name())
rospy.spin()
##* CMakeLists.txt と package.xml の編集
恒例の編集作業。
下図の CMakeLists.txt と package.xml に対し Action を使用するうえで必要となるビルド指定を追加する。
###CMakeLists.txt の編集
find_package(catkin REQUIRED COMPONENTS
roscpp
rospy
std_msgs
message_generation
actionlib_msgs #<--- 追加
actionlib #<--- 追加
)
add_action_files(
FILES
fibonacci.action
)
generate_messages(
DEPENDENCIES
std_msgs
actionlib_msgs #<--- 追加
)
###package.xml の編集
<build_depend>actionlib</build_depend>
<build_depend>actionlib_msgs</build_depend>
<exec_depend>actionlib</exec_depend>
<exec_depend>actionlib_msgs</exec_depend>
##* Action のビルドと実行
$ cd ~/catkin_ws
$ catkin_make
予想以上に長いビルドログが出力されてビックリした。
-- tutorials: 8 messages, 1 services
って、そんなにたくさん Message は自作していないんですけど。。。
**実行ログ**
Base path: /home/pi/catkin_ws
Source space: /home/pi/catkin_ws/src
Build space: /home/pi/catkin_ws/build
Devel space: /home/pi/catkin_ws/devel
Install space: /home/pi/catkin_ws/install
####
#### Running command: "make cmake_check_build_system" in "/home/pi/catkin_ws/build"
####
-- Using CATKIN_DEVEL_PREFIX: /home/pi/catkin_ws/devel
-- Using CMAKE_PREFIX_PATH: /home/pi/catkin_ws/devel;/opt/ros/kinetic
-- This workspace overlays: /home/pi/catkin_ws/devel;/opt/ros/kinetic
-- Using PYTHON_EXECUTABLE: /usr/bin/python
-- Using Debian Python package layout
-- Using empy: /usr/bin/empy
-- Using CATKIN_ENABLE_TESTING: ON
-- Call enable_testing()
-- Using CATKIN_TEST_RESULTS_DIR: /home/pi/catkin_ws/build/test_results
-- Found gtest sources under '/usr/src/gtest': gtests will be built
-- Using Python nosetests: /usr/bin/nosetests-2.7
-- catkin 0.7.8
-- BUILD_SHARED_LIBS is on
-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-- ~~ traversing 1 packages in topological order:
-- ~~ - tutorials
-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-- +++ processing catkin package: 'tutorials'
-- ==> add_subdirectory(tutorials)
-- Using these message generators: gencpp;geneus;genlisp;gennodejs;genpy
-- Generating .msg files for action tutorials/fibonacci /home/pi/catkin_ws/src/tutorials/action/fibonacci.action
Generating for action fibonacci
-- tutorials: 8 messages, 1 services
-- Configuring done
-- Generating done
-- Build files have been written to: /home/pi/catkin_ws/build
####
#### Running command: "make -j4 -l4" in "/home/pi/catkin_ws/build"
####
Scanning dependencies of target _tutorials_generate_messages_check_deps_fibonacciGoal
Scanning dependencies of target actionlib_msgs_generate_messages_py
[ 0%] Built target std_msgs_generate_messages_py
[ 0%] Built target actionlib_msgs_generate_messages_py
Scanning dependencies of target _tutorials_generate_messages_check_deps_fibonacciActionGoal
[ 0%] Built target _tutorials_generate_messages_check_deps_greet
[ 0%] Built target _tutorials_generate_messages_check_deps_fibonacciGoal
Scanning dependencies of target _tutorials_generate_messages_check_deps_fibonacciAction
Scanning dependencies of target _tutorials_generate_messages_check_deps_fibonacciActionResult
[ 0%] Built target _tutorials_generate_messages_check_deps_hello
[ 0%] Built target _tutorials_generate_messages_check_deps_fibonacciActionGoal
Scanning dependencies of target _tutorials_generate_messages_check_deps_fibonacciActionFeedback
Scanning dependencies of target _tutorials_generate_messages_check_deps_fibonacciFeedback
[ 0%] Built target _tutorials_generate_messages_check_deps_fibonacciAction
[ 0%] Built target _tutorials_generate_messages_check_deps_fibonacciActionResult
Scanning dependencies of target _tutorials_generate_messages_check_deps_fibonacciResult
Scanning dependencies of target actionlib_msgs_generate_messages_lisp
[ 0%] Built target _tutorials_generate_messages_check_deps_fibonacciActionFeedback
[ 0%] Built target _tutorials_generate_messages_check_deps_fibonacciFeedback
[ 0%] Built target actionlib_msgs_generate_messages_lisp
Scanning dependencies of target actionlib_msgs_generate_messages_cpp
[ 0%] Built target std_msgs_generate_messages_lisp
[ 0%] Built target std_msgs_generate_messages_cpp
[ 0%] Built target actionlib_msgs_generate_messages_cpp
Scanning dependencies of target actionlib_msgs_generate_messages_eus
[ 0%] Built target _tutorials_generate_messages_check_deps_fibonacciResult
Scanning dependencies of target actionlib_msgs_generate_messages_nodejs
[ 0%] Built target actionlib_msgs_generate_messages_eus
[ 0%] Built target std_msgs_generate_messages_eus
[ 0%] Built target actionlib_msgs_generate_messages_nodejs
[ 0%] Built target std_msgs_generate_messages_nodejs
[ 2%] Generating Python from MSG tutorials/fibonacciActionFeedback
[ 4%] Generating Lisp code from tutorials/fibonacciActionFeedback.msg
[ 6%] Generating C++ code from tutorials/fibonacciActionFeedback.msg
[ 8%] Generating EusLisp code from tutorials/fibonacciActionFeedback.msg
[ 10%] Generating Lisp code from tutorials/fibonacciFeedback.msg
[ 12%] Generating EusLisp code from tutorials/fibonacciFeedback.msg
[ 14%] Generating Lisp code from tutorials/greet.msg
[ 16%] Generating Python from MSG tutorials/fibonacciFeedback
[ 18%] Generating Lisp code from tutorials/fibonacciActionResult.msg
[ 20%] Generating EusLisp code from tutorials/greet.msg
[ 22%] Generating C++ code from tutorials/fibonacciFeedback.msg
[ 25%] Generating Python from MSG tutorials/greet
[ 27%] Generating EusLisp code from tutorials/fibonacciActionResult.msg
[ 29%] Generating Lisp code from tutorials/fibonacciGoal.msg
[ 31%] Generating Lisp code from tutorials/fibonacciResult.msg
[ 33%] Generating EusLisp code from tutorials/fibonacciGoal.msg
[ 35%] Generating Python from MSG tutorials/fibonacciActionResult
[ 37%] Generating Lisp code from tutorials/fibonacciActionGoal.msg
[ 39%] Generating Lisp code from tutorials/fibonacciAction.msg
[ 41%] Generating C++ code from tutorials/greet.msg
[ 43%] Generating EusLisp code from tutorials/fibonacciResult.msg
[ 45%] Generating Lisp code from tutorials/hello.srv
[ 47%] Generating Python from MSG tutorials/fibonacciGoal
[ 47%] Built target tutorials_generate_messages_lisp
[ 50%] Generating EusLisp code from tutorials/fibonacciActionGoal.msg
[ 52%] Generating Javascript code from tutorials/fibonacciActionFeedback.msg
[ 54%] Generating Python from MSG tutorials/fibonacciResult
[ 56%] Generating EusLisp code from tutorials/fibonacciAction.msg
[ 58%] Generating C++ code from tutorials/fibonacciActionResult.msg
[ 60%] Generating EusLisp code from tutorials/hello.srv
[ 62%] Generating Python from MSG tutorials/fibonacciActionGoal
[ 64%] Generating Javascript code from tutorials/fibonacciFeedback.msg
[ 66%] Generating EusLisp manifest code for tutorials
[ 68%] Generating Javascript code from tutorials/greet.msg
[ 70%] Generating Python from MSG tutorials/fibonacciAction
[ 72%] Generating C++ code from tutorials/fibonacciGoal.msg
[ 75%] Generating Javascript code from tutorials/fibonacciActionResult.msg
[ 77%] Generating Javascript code from tutorials/fibonacciGoal.msg
[ 79%] Generating Python code from SRV tutorials/hello
[ 81%] Generating Javascript code from tutorials/fibonacciResult.msg
[ 83%] Generating C++ code from tutorials/fibonacciResult.msg
[ 85%] Generating Javascript code from tutorials/fibonacciActionGoal.msg
[ 87%] Generating Python msg __init__.py for tutorials
[ 89%] Generating Javascript code from tutorials/fibonacciAction.msg
[ 91%] Generating Javascript code from tutorials/hello.srv
[ 91%] Built target tutorials_generate_messages_eus
[ 93%] Generating Python srv __init__.py for tutorials
[ 93%] Built target tutorials_generate_messages_nodejs
[ 95%] Generating C++ code from tutorials/fibonacciActionGoal.msg
[ 97%] Generating C++ code from tutorials/fibonacciAction.msg
[100%] Generating C++ code from tutorials/hello.srv
[100%] Built target tutorials_generate_messages_py
[100%] Built target tutorials_generate_messages_cpp
[100%] Built target tutorials_generate_messages
とにもかくにも一度実行してみる。
$ roscore
$ cd catkin_ws
$ source devel/setup.bash
$ rosrun tutorials fibonacci_server.py
何も起こらない。。。Client が起動していないから当然か。
ここで、Message の生成数が8個だったのがどういう理由なのか気になったので、別のターミナルを起動し、どのような TOPIC が生成されているのかを念の為確認しておく。
rostopic list
というコマンドを使用すると確認できるそうな。
$ cd ~/catkin_ws
$ source devel/setup.bash
$ rostopic list -v
あっ、5つも増えてる。。。
catkin_make
コマンドを実行すると Action の実装に必要な Message/TOPIC を自動的に生成してくれるということかな。
-
Published topics:
- /fibonacci/feedback [tutorials/fibonacciActionFeedback] 1 publisher
- /fibonacci/status [actionlib_msgs/GoalStatusArray] 1 publisher
- /fibonacci/result [tutorials/fibonacciActionResult] 1 publisher
-
Subscribed topics:
- /fibonacci/goal [tutorials/fibonacciActionGoal] 1 subscriber
- /fibonacci/cancel [actionlib_msgs/GoalID] 1 subscriber
では、Clientを起動してみる。
$ cd catkin_ws
$ source devel/setup.bash
$ rosrun tutorials fibonacci_client.py
まず、Server側に「Executing, creating fibonacci ...」と表示され、しばらく待っていると、「Succeeded」と表示された。
Server側の「Succeeded」の表示とほぼ同時に、Client側にResult値であるフィボナッチ数列20個分が一斉に表示された。
#◆ 本日のまとめ
- フィボナッチ、 フィボナッチ、 フィボナッチ... 20回噛まずに言えたら大したもんです
- Action形式、色々なシチュエーションで使える気がする
- むしろ、Service形式いらなくない?
- Feedback を拾っていないし、Cancel の動きは試していないけど、そこはご愛嬌で
- Python3系の構文がPython2系で使えるようになる
__future__
という超便利なmoduleがあることを知る
#◆ 次回予告
無謀にもわずか5日目にしてセンサーか何かの制御にチャレンジしたいと思う。
調べごとなどが増えたり、実装につまづく部分が多かったりと、記事をすぐに書けない可能性があるが、頑張る。
オジサン頑張る。
情熱だけで無理やり乗り切る。
乗りきれるといいな。。。
#◆ 次回記事
RaspberryPi3とZumoとROSで半永久自走式充放電ロボを作成したい_005日目_Arduino+モーター制御 に続く