7
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

ROSAdvent Calendar 2021

Day 18

D言語でROS2(再走)

Last updated at Posted at 2021-12-18

はじめに

昨年D言語ROS2をやるためにros2_dというプロジェクトを作りました。PublisherSubscriptionを作り、最低限の通信ができるところまでは実装しました。作ったものは主に以下の3つです。

  • C APIであるrclのD言語バインディング
    • ABI互換があるため、dppによる自動生成
  • rosidl_generator を使ったD言語向けメッセージ型生成機能
    • D言語構造体の自動生成
    • C言語構造体 <-> D言語構造体の相互変換
  • D言語向けROS2クライアントライブラリであるrcld

何故これらが必要なのかは去年私が書いた記事を参考にしてもらうとして、一応これを発展させればD言語でROS2をやるという目的は達成されます。しかしながら、直感的に使えないと思った部分があり、結局放置して

  • D言語のビルドシステム兼パッケージマネージャのDuBをcolcon及びamentで使うためのcolcon_d
  • ROS2向けvscodeプラグインのvscode-ros2 (使って)

を作るなど、外堀だけは埋めていました。微妙だった点としては、

  • メッセージパッケージのリビルドが必要
    • これには /opt/ros/$ROS_DISTRO に置かれているものも含まれる
  • メッセージパッケージのリビルドが遅い
    • 遅い
  • ROS2のビルドプロセス(colcon)に乗る必要がある
  • libclang が必要
    • 可能な限り必要部品を減らしたい

などがありました。

このうち「libclangが必要」というののはC言語のヘッダーファイルからD言語向けの宣言を自動生成するためのものでした。これについては、最近D言語はC言語のコンパイラになったため解決できるかもしれません。実際試してみたのがこちら。

今今は機能が足りていないと思うので、将来的に移行することを考えています。

他方、メッセージの扱いは大変です。rosidl_generatorを使うとどうしてもROS2のビルドプロセスに潜り込ませる必要があったのです。ただし、これについてはGalacticで改善しているかもしれませんが、2023年まではFoxyも必要でしょうし、Pythonであることは変わりません(察して)。ROS2のビルドプロセスに乗る必要があるのも微妙です。私は自前で言語のビルドシステムとROS2のビルドシステムを連携させる機能を作ったので package.xmldub.json(マニフェストファイル)だけで良くなりますが、そうでなければ CMake を使って強引に連携させる必要がありますし、どちらにしてもビルドコマンドはcolcon buildになります。慣れ親しんだ各言語のビルドコマンドを使いたいです。

これらの課題を解決できなくて不貞寝していたのですが、r2rrclrustといったROS2のビルドシステムに乗らない方法を見かけました。クライアントライブラリではありませんが、intdash_ros2bridgeもビルドシステムの外側からメッセージ定義を抽出していて面白いです。

そこで、私もROS2のビルドシステムの外側に乗らない形でros2_dを作り直しました。では、本編。(内部実装だけに興味あるならここまでスキップ)

何故D言語でROS2?

デモ

これは std_msgs/msg/String である /chatter トピックをサブスクライブして WebSocket 経由で ブラウザに送っているデモです。D言語で著名なウェブフレームワークである Vibe.d を使っています。解説記事を見ながら1時間ぐらいででっちあげました。サーバ側のコードは以下のようになります。クライアント側は解説記事のままです。

import std;
import vibe.vibe;
import rcld;
import std_msgs.msg : String;


void main() {
    auto settings = new HTTPServerSettings(":8080");

    auto router = new URLRouter;
    router.get("/ws", handleWebSockets(&handler));

    auto listener = listenHTTP(settings, router);
    scope (exit)
        listener.stopListening();

    runTask(&startDman);
    runApplication();
}

WebSocket[] sockets;

void handler(scope WebSocket socket) {
    logInfo("CONNECTED");
    sockets ~= socket;
    while (socket.waitForData){}
    logInfo("DISCONNECTED");
    sockets = sockets.filter!((s) => s !is socket).array;
}

void startDman() {
    auto cxt = new Context();
    auto node = new Node("vibe", "", cxt);
    auto sub = new Subscription!String(node, "/chatter");

    while (true) {
        sleep(10.msecs);
        String msg;
        if (sub.take(msg))
            sockets.each!((s) { s.send(msg.data); });
    }
}

何が嬉しいのか

startDmanという関数の中でノードを作りサブスクリプションを作り、take関数でメッセージを受信してWebクライアントに送っています。複雑さのない、ある意味ただそれだけのコードです。しかし、以下の点に価値があると考えています。

「ウェブフレームワークであるVibe.dを使っています」

dub add vibe-d とタイプするだけで使えるようになります。静的型付け言語でありながら簡単にWebアプリを作れるのは利点だと思います。

DuB registryで公開されているパッケージを簡単に導入でき、Github 上のパッケージも導入できます。ROS2では画像や点群などの信号処理が出てくると思います。そういう時はndsliceという多次元配列を提供するmir-algorithmが使えるかなと考えています。解説記事

D言語である(ダイレクトマーケティング)

今の時代、強烈な機能というものはもはや思い浮かびませんが、色々使いやすいなーと思う点はあります。

実際のところ私自身D言語の強力な機能を使いこなせていないと思います。機能は他にも色々あります。例えばUDA(解説記事)を使って、PublisherSubscription の QoS を設定できたら面白いかなと思いますし、PublisherSubscription の一覧を(コンパイル時に) 抽出できないかと考えています。例えば以下のような感じですね。

// コンパイル時にトピック名を決めておく
// QoS を属性として付与
class Talker : Node {

    @reliability("best_effort")
    Publisher!(String, "/chatter") pub;
}

class Listener : Node {

    @reliability("reliable")
    Subscription!(String, "/chatter") sub;
}

// Publisher, Subscription の一覧をコンパイル時に取得
enum talkerInfo = parseIF!Talker;
enum listenerInfo = parseIF!Listener;

// TalkerとListenerは QoSの問題で通信できないのでコンパイルエラー
static assert(validateConnection(talkerInfo, listenerInfo));

ROS2を使う開発者は基本的にアルゴリズムに集中したいのであり、トピックが繋がっているのかの確認をやりたくないですし、(作り方にもよるんでしょうが)コンストラクタの実装を見てノードのインターフェースやパラメータを調べたいわけでもありません。やりたくないことは機械にほうり投げましょう。そして簡単にほうり投げられるようにしたいですね。

また、狙いどころとしてはrclpyが担っている部分の代替も考えています。Pythonで速いコードを書くのはちょっと大変です。NumPyPCLを上手く使えば良いかもしれませんが、最初から速い言語であれば気に病むこともないかもしれません。静的型付け言語で負荷がそれなりにある処理が必要な「ツール」をさくっと作りたい時なんかの選択肢を提示できればと思います。

まあ、私がD言語を書きたいだけなんですが。

ros2_d

ようやく実装の話をします。

基本方針

目標は以下のものです。

  • 可能な限り、D言語のビルドコマンドである dub build だけでビルドできるようにする

そのために必要なのは、

  1. ROS2 のメッセージ定義からD言語の定義を生成する
  2. これを DuB で参照できるようにする
  3. rcl の C API をD言語から参照できるようにする
  4. D言語向けクライアントライブラリの rcld を作る

です。各要素の流れを図示すると以下のようになると思います。

Screen Shot 2021-12-18 at 19.56.19.png

では、各要素を解説していきます。なお、全てD言語で書いています。

1. ROS2 のメッセージ定義からD言語の定義を生成する

メッセージ定義の在処

ROS2はメッセージ定義をROS2パッケージという形で提供しています。std_msgssensor_msgs ですね。それ以外でも、ユーザ定義メッセージも作ることができますが、これらは全て「ROS2パッケージ」のうち、<member_of_group>rosidl_interface_packages</member_of_group> であるものをさします。ROS2パッケージがどこにあるかは環境変数 AMENT_PREFIX_PATH で表現されます。この環境変数は source install/setup.bash などで設定されます。

つまり、メッセージ定義を探し出すには環境変数AMENT_PREFIX_PATHを見てpackage.xmlを見て指定のタグを確認すれば良い、ということになります。実装としてはこの辺になります。

メッセージ定義のパース

rclrust の記事で触れられていますが、ROS2の型生成では *.idl というファイルが生成され、インストールディレクトリに配置されます(/opt/ros/$ROS_DISTRO/share/std_msgs/msg を見てみればわかります)。

また、背景はわかりませんが、最初から *.idl として定義しているケースもあるようです。他の人と同じようなことをやっても芸がないなとも思ったので、ros2_d では *.idl をパースすることにしました。

IDLファイルはそれなりに複雑は表現であるため、PEG文法を導入することにし、ライブラリとしてはpeggedを使用しました。今のところ*.msgが基のIDLファイルしか試していないですが、定義はこちらです。若干peggedの方言が入っているような気がしますが、D言語以外の環境でも使えるかもしれません。(ros2_d:msg_genのパース機能だけを使う、というのもできるので任意の言語向けに生成もできるはず)

メッセージ定義の生成

生成にはmustache-dを使いました。Mustache自体はD言語固有のテンプレートエンジンというわけではありません。必要なものを生成するためのテンプレートを頑張って作ります。

コードはこの辺

2. これを DuB で参照できるようにする

`DuBプロジェクトでは依存パッケージを以下のように記述します。

"dependencies": {
    "std_msgs": ">=0.0.1",
    ...
},

今回はメッセージ定義を依存パッケージとして認識させる必要があります。完全にD言語の文脈になりますが、DuBプロジェクトでは .dub/packages 以下にパッケージを適切に設置すれば読み込んでくれるようです。これは、各DuBプロジェクト毎にメッセージ定義を持たせるということになります。いずれ不都合が出る気がしますが、とりあえずは。

3. rcl の C API をD言語から参照できるようにする

dpp を使いました。以下のコマンドでD言語向けの宣言を作れます。すごい。

CC=clang dub run -y dpp -- \
    --preprocess-only \
    --include-path /opt/ros/${ROS_DISTRO}/include \
    source/rcl/package.dpp

CC=clangと書いている通り、clangに依存しています。前述の通りD言語はC言語を読めるようになるので、そこに期待です。

4. D言語向けクライアントライブラリの rcld を作る

頑張って作る。基本的な話は去年しています

使い方

この辺を参考に。
この記事を書いている時に気づきましたが、pub/sub検証のユニットテストを書きましたが Example 書いてねぇ。

さいごに

ここまであえて触れていませんでしたが、この記事を書いている時点で機能は全然揃っていません。サービスやアクションは対応していないですし、簡単に作った Executor もかなり微妙です。rcldPublisherSubscriptionのデモのためだけの実装と言え、そこにはいかなる哲学もありません。なので、これからのんびり育てていくことになると思います。プログラミングに疲れたら使ってみてください。

7
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?