Python
ROS
ROSDay 23

roslaunchのコードAPIを使う part2

Day9の記事を受けて。
https://qiita.com/Nasupl_r/items/a1449cf192c57f03ffa5

「Q. roslaunchをPythonから叩きたいやつおるか?w」

A. ここにおるでw

場面によっては使えるツールだと思います

例えばテキトーにwrapして…

import roslaunch
import os

class SimpleRosLaunch(object):
    def __init__(self, ):
        self.launchfile = ""
        self.reinitialize()

    def __del__(self,):
        self.stop()

    def load_file(self, launchfile):
        if self.is_alive():
            raise RuntimeError("you need to stop before you load another launch file")
        self.launchfile = os.path.abspath(os.path.expanduser(launchfile))
        self.launch.parent.config = roslaunch.config.load_config_default([self.launchfile], None)

    def start(self,):
        local_machine = roslaunch.core.local_machine()
        for n in self.launch.parent.config.nodes:
            n.machine = local_machine
        self.launch.start()

    def is_alive(self,):
        if self.launch.parent.pm is not None and self.launch.parent.pm.is_alive() and len(self.launch.parent.pm.get_active_names())>0:
            return True
        return False

    def current_file(self, ):
        return self.launchfile

    def reinitialize(self, ):
        self.launch = roslaunch.scriptapi.ROSLaunch()

    def stop(self,):
        self.launch.stop()
        self.reinitialize()
        self.load_file(self.launchfile)

こうしてやると…

import os
import roslaunch
import rospkg
from simple_roslaunch import SimpleRosLaunch

# 初期化
simple_roslaunch = SimpleRosLaunch()
# signalをいい感じに処理する
roslaunch.pmon._sig_initialized = True
rospy.on_shutdown(simple_roslaunch.stop)
launchfile = os.path.join(rospkg.RosPack().get_path('sample_pkg'), 'launch', 'sample.launch')

simple_roslaunch.load_file(launchfile)


# スタート
if simple_roslaunch.is_alive():
    rospy.logerr("you've already executed launch file")
    return False
simple_roslaunch.start()
return True

# ストップ
if not simple_roslaunch.is_alive():
    rospy.logerr("no launch file is launched")
    return False
simple_roslaunch.stop()

起動したり止めたりできます

なにが嬉しいかというと、
例えばOccupancyGrid(/map)を保存したい時に、

<launch>
    <node pkg="map_server" type="map_saver" name="map_saver">
    </node>
</launch>

のようなlaunch fileを準備しておき、

from map_msgs.msg import SaveMap

def run_map_saver(self, req):
    global simple_roslaunch
    try:
        for n in simple_roslaunch.launch.parent.config.nodes:
            if (n.package == "map_server" and n.type == "map_saver"):
                # <node>のargsが変更できる
                n.args = unicode('-f %s'%(req.filename.data, ))
        # rosparamも変更できる
        # simple_roslaunch.launch.parent.config.params['/hoge/fuga/piyo'].value = '...'
        simple_roslaunch.start()
        while not rospy.is_shutdown():
            rospy.loginfo("saving map...")
            if not simple_roslaunch.is_alive():
                rospy.loginfo("saving map done!")
                break
            rospy.sleep(0.2)
        simple_launch.stop()
    except Exception as e:
        rospy.logerr(e)

sample_service = rospy.Service('~sample_service', SaveMap, run_map_saver)

このようにlaunch fileに定義された引数などを動的に変更できます
mapに何か後処理を加えて保存したい場合もlaunch fileに後処理ノードを加えて、<remap>でmap_saverとtopicを繋げばよい、ということになります

当然ながらlaunch fileをわざわざ準備する必要もなくて

launch = roslaunch.scriptapi.ROSLaunch()
node = roslaunch.core.Node('map_server', 'map_saver')
node.args = unicode('-f %s'%(req.filename.data, ))
# namespaceも変更できる
# node.namespace = '/hogefuga'
launch.start()
launch.launch(node)
while not rospy.is_shutdown():
    rospy.loginfo("saving map...")
    if len(launch.parent.pm.get_active_names()) == 0:
        rospy.loginfo("saving map done!")
        break
    rospy.sleep(0.2)
launch.stop()
del launch

その場でプロセスが起動できちゃう!プロセス管理はroslaunchにお任せ!(?)

まとめ

roslaunch APIは very unstableです
なるべく使わないようにしましょう