ROS の wildcard Subscriber / Publisher を作った

MQTT で subscribe 時にワイルドカードを使えるのを見て、ROS でも使えたらいいのに！と思ったので、
トピック名をワイルドカード指定して subscribe / publish できるユーティリティを作りました。

# ros_wild の紹介

## 概要

ROS で使えるワイルドカード Publisher, Subscriber ユーティリティです。

https://github.com/yuma-m/ros_wild から clone して使えます。

```bash
$ git clone https://github.com/yuma-m/ros_wild.git /path/to/your/catkin_ws/src/ros_wild
$ cd /path/to/your/catkin_ws && catkin_make
```

## 使い方(コマンドライン)

### ros_wild echo

`$ rosrun ros_wild echo "トピック名の正規表現"` のように使います。

例えば、全トピックの内容を echo してみると、以下のような感じで、トピック名、メッセージの型とメッセージの中身が表示されます。

```bash
$ rosrun ros_wild echo ".+"
---
topic: /ultrasound_sensor
type: sensor_msgs/Range

header: 
  seq: 1
  stamp: 
    secs: 1478776655
    nsecs:    143582
  frame_id: ultrasound_sensor
radiation_type: 0
field_of_view: 0.569999992847
min_range: 0.20000000298
max_range: 3.0
range: 0.600000023842
---
topic: /rosout
type: rosgraph_msgs/Log

header: 
  seq: 1
  stamp: 
    secs: 1479382593
    nsecs: 476881255
  frame_id: ''
level: 4
name: /foo_pkg/bar_node
msg: this is example message
file: /home/foo/catkin_ws/src/foo_pkg/src/bar_node.cpp
function: main
line: 170
topics: ['/rosout', '/foo_pkg/example']
```

僕は同一種類の複数のセンサー値を確認する時によく利用します。
トピック毎にターミナルを開いていると、それだけで画面がいっぱいになってしまうので。

## 使い方 (Python)

### 利用例 (Subscriber)

`/bool/.+` のようなトピックにまとめて subscribe して、 callback メソッドを設定する場合は、以下のような感じで書きます。

```py
from ros_wild import Subscriber

def callback_bool(msg):
    print(msg.data)

sub = Subscriber(r"/bool/.+", callback=callback_bool)
print("Subscribed topics are {}".format(sub.subscribed_topics))
```

実行結果

```
Subscribed topics are ['/bool/01', '/bool/02', '/bool/03']
True
True
False
...
```

### rospy.Subscriber との違い

`rospy.Subscriber` との違いは、2つ目の引数である `data_class` が不要なことです。

- rospy の場合
    - `rospy.Subscriber("/foo/bar", std_msgs.msg.String, callback_func, callback_args="hoge", ...)`
- ros_wild の場合
    - `ros_wild.Subscriber("/foo/bar", callback_func, callback_args="hoge", ...)`

つまり、メッセージ型を指定せずにトピックに subscribe することが出来ます。

これは便利な半面、思わぬバグや例外を招く危険性があり、使い方には注意が必要です。
そこで `register_callback` メソッドを利用して以下のように書いてあげることで、より安全な実装が可能です。


### 利用例 (register_callback)

トピック名の正規表現のみを指定して `Subscriber` を作成し、 後から `data_class` の種類毎に異なるコールバックを登録することが出来ます。
これにより、トピックのメッセージ型が想定した型と異なる場合のエラーを防ぐことができます。

```py
from ros_wild import Subscriber
from std_msgs.msg import Bool, String

def callback_bool(msg):
    print("bool is {}".format(msg.data))

def callback_string(msg):
    print("string is {}".format(msg.data))

sub = Subscriber(r".+")
print("Subscribed topics are {}".format(sub.subscribed_topics))
sub.register_callback(Bool, callback_bool)
sub.register_callback(String, callback_string)
print("Topics with callback are {}".format(sub.topics_with_callback))

```

実行結果

```
Subscribed topics are ['/bool/01', '/rosout', 'rosout_agg', '/string/01']
Topics with callaback are ['/bool/01', '/strings/01']
bool is True
string is foo bar
...
```

### 利用例 (Publisher)

使い方は自分でもよく分かりませんが、一応 Publisher も作りました。
複数のロボットに同一のコマンドを送りたいとか、rviz で全 Range センサーに円錐を表示したいとか、そんな用途に使う...のかなぁ。

```py
import rospy
from ros_wild import Publisher
from geometry_msgs.msg import Range

# publish to all Range topics
rospy.init_node("test")
pub = Publisher("/range/.+", queue_size=1)
pub.publish(Range(field_of_view=0.314, range=1.0))
```

## 今後の課題

### パッケージに登録

まだ反映はされていませんが、ROS のパッケージリポジトリに登録しました。
最初はパッケージの登録の仕方が全然分からなかったのですが、[bloom のチュートリアル](http://wiki.ros.org/bloom/Tutorials)に丁寧に記述されています。

基本的には上記チュートリアルの [Releasing a package for the first time](http://wiki.ros.org/bloom/Tutorials/FirstTimeRelease) に則って、コマンドを実行していくだけで、releaseリポジトリへのコミットやrosdistroへのプルリクエストの作成を自動で行ってくれます。
ビルド自体はプルリクのマージから24-48時間以内に行われるのですが、debian パッケージとして apt でインストールできるようになるまでは数週間かかるようです。

### 機能拡張

今後の課題として以下の機能追加を考えています。
プルリク大歓迎です、お待ちしています！

- `Subsciber`, `Publisher` 作成時のメッセージ型による絞り込み
- C++ 対応
- (機能拡張じゃないけど) ros_wild じゃなくて roswild に名前を変えたほうが良いかも...