OSOYOO PI CARキット ROS2化 #2 – Web ServerとROS2ノードとの接続

OSOYOO PI CAR

ROS2には、Web ServerとROS2ノードを接続するためのツール「rosbridge_suite」が用意されています。「rosbridge_suite」は、ROS2にJSONインターフェースを提供しているようです。
ROS2 topic、ROS2 servicesにアクセスするための情報をJSON形式で設定できるようです。

RobotWebTools/rosbridge_suite

ROS2 topic、servicesがJSONを介してブラウザ閲覧可能になれば、カメラ画像や各種情報を表示することも可能になる思います。また、すでにたくさんの方が実際に使って制御されているようです。
rosbridge_suiteは、前提としてWeb Serverを起動しておかないと動作しません。
Raspberry PI3B+を使用しますので、Web Serverにはapache2を使用します。
私は、apacheに関して知見はありませんので、色々と調べながら実施しました。
それよりも、大変だったのは、rosbridge_suiteを動作させることでした。
理解するまでに時間がかかってしまいました。

まずは、apache2を下記手順で導入していきます。

$ sudo apt update
$ sudo apt install apache2 -y

混乱したのは、この先で設定ファイル類があります。
導入直後、ファイル、ディレクトリの意味を理解できませんでしたが、/etc/apache2/apach2.conf内にファイル、ディレクトリの役割記載がありました。

〇 ディレクトリ構成

/etc/apache2は、下記のようなディレクトリ構成になっています。

/etc/apache2/
|-- apache2.conf
|     `-- ports.conf
|-- mods-enabled
|    |-- *.load
|    `-- *.conf
|-- conf-enabled
|    `-- *.conf
`-- sites-enabled
      `-- *.conf

ファイルやディレクトリの意味が分からなかったのですが、apache2.confを見るとそれらの概要が以下のように記載されていました。

ファイル/ディレクトリ役割
apache2.conf共通設定用コンフィグレーションファイル。
ports.conf待ち受けポート番号設定。
mods-enableda2enmod/a2dismodで有効化されたモジュールのsymbolic linkが導入される。
a2enmod/a2dismodで有効化、無効化可能なモジュールは、mods-avalable内に格納される。
conf-enableda2enconf/a2disconfで有効化、無効化されたコンフィグレーションのsymbolic linkが導入される。
a2enconf/a2disconfで有効化、無効化可能なコンフィグレーションは、conf-availableに格納される。
sites-enableda2ensite/a2dissiteで有効化、無効化されたバーチャルホスト設定のsymbolic linkが導入される。
a2ensite/a2dissiteで有効化、無効化可能なバーチャルホストは、sites-availableに格納される。

※ インストール時には、000-default.confが有効になっている。
ファイル/ディレクトリの意味

〇 バーチャルホスト設定

インストール時のデフォルトは、/var/www/htmlにルート設定されていますが、picar専用のバーチャルホスト設定ファイルを/etc/apache2/sites-available/Pycar_host.confとして作成します。
(000-default.confをコピー後に修正)

<VirtualHost *:80>
        ServerName picar                    ← サーバー名
        ServerAdmin webmaster@localhost
        DocumentRoot /var/www/html/picar               ← サイトのルート
        ErrorLog ${APACHE_LOG_DIR}/error.log
        CustomLog ${APACHE_LOG_DIR}/access.log combined
        <Directory /var/www/html/picar>                ← apache2.confをまねて追加
            Options FollowSymlinks Includes
            AllowOverride All
            AddType text/html .html
            Require all granted
         </Directory>
</VirtualHost>
# vim: syntax=apache ts=4 sw=4 sts=4 sr noet

上記ファイルを作成後、下記のように環境をセットアップしapacheを再起動します。

$ cd /var/www
$ sudo mkdir picar
$ sudo echo "Hello World!" > picar/index.html
$ sudo a2ensite PiCar_host.conf
$ sudo a2dissite 000-default.conf 
$ sudo systemctl start apache2
$ sudo systemctl enable apache2

設定後、下記のようにwgetでindex.htmlが取得できることを確認します。

$ wget http://localhost/index.html

rosbridge-suiteのセットアップを行います。
rosbridge-suiteは、nodejs、npmを使用しますので、同様に入れておきます。

$ sudo apt-get install -y ros-humble-rosbridge-suite
$ sudo apt-get install -y nodejs npm

roslibjs(JavaScript library)を使用してHTMLコードを作成します。
HTMLコードの書き方については、自身には知見はないので、「ROS Wiki roslibjs 5.Tutorial」を頼りに書いていきます。
今回は、BasicRosFunctionalityを解析しながら作成してみました。
このサンプルはROSのTOPIC, SERIVE, parameter等の基本IFを解説してあります。
そのままではROS2で動作不可ですのでROS2用に移植してお勉強しました。
実際に作成したコードは、subscription, publishだけですが、以下の手順で確認しておきました。

$ ros2 launch rosbridge_server rosbridge_websocket_launch.xml      # rosbridge service起動
$ ros2 topic pub /listener std_msgs/msg/String "{data: hello world}" # subscription評価
$ ros2 topic echo /cmd_vel                                         # publish評価

ブラウザのコンソールとbashコンソールに表示される情報を頼りに結果を確認しました。
ここまでできれば、後はカメラ画像をとらえて表示するだけとなります。

ブラウザおよびコンソール画面

〇 参考:ROS2版BasicRosFunctionality.html

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />

<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/eventemitter2@6.4.9/lib/eventemitter2.min.js"></script>
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/roslib@1/build/roslib.min.js"></script>

<script type="text/javascript">
    //
    // this connects to to Ros bridge server on local host.
    // -----------------
    var ros = new ROSLIB.Ros({
        url : 'ws://192.168.11.202:9090',
    });

    // this adds connection listener.
    ros.on('connection', function() {
        console.log('Connected to websocket server.');
    });
    // this adds error listener.
    ros.on('error', function(error) {
        console.log('Error connecting to websocket server: ', error);
    });

    // this adds close listener.
    ros.on('close', function() {
        console.log('Connection to websocket server closed.');
    });

    // this creates topic to subscribe or publish or both.
    // ------------------
    var cmdVel = new ROSLIB.Topic({
        ros : ros,
        name : '/cmd_vel',
        messageType : 'geometry_msgs/msg/Twist'
    });

    // this creates a new message to publish.
    var twist = new ROSLIB.Message({
    linear : {
      x : 0.1,
      y : 0.2,
      z : 0.3
    },
    angular : {
      x : -0.1,
      y : -0.2,
      z : -0.3
    }
    });
    cmdVel.publish(twist);

    // this subscribes topic.
    // ----------------------
    var listener = new ROSLIB.Topic({
        ros : ros,
        name : '/listener',
        messageType : 'std_msgs/msg/String'
    });
    listener.subscribe(function(message) {
        console.log('Received message on '+listener.name + ': ' + message.data);
        listener.unsubscribe();
    });

    // this requests service.
    // -----------------
    var addTwoIntsClient = new ROSLIB.Service({
        ros : ros,
        name : '/add_two_ints',
        serviceType : 'example_interfaces/srv/AddTwoInts'
    });

    // this creates a requirement.
    var request = new ROSLIB.ServiceRequest({
        a : 1,
        b : 2
    });

    // this gets the result of service.
    addTwoIntsClient.callService(request, function(result) {
        console.log('Result for service call on '
            + addTwoIntsClient.name
            + ': '
            + result.sum);
    });

    // this adds parameter lisener.
    // ---------------------------------
    ros.getParams(function(params) {
        console.log('Params: ' + params);
    });

    // this is the parameter of max_val_x.
    var maxVelX = new ROSLIB.Param({
        ros : ros,
        name : 'max_vel_x'
    });

    maxVelX.set(0.8);

</script>
</head>

<body>
  <h1>Simple roslib Example</h1>
  <p>Check your Web Console for output.</p>
</body>
</html>

今回は、WebServerとROS2ノードの接続までで、時間いっぱいになってしまいました。
ROS2でWebServerを扱おうとすると結構面倒くさいように思います。
ROS2は色々なことができるパーツがそろってはいるようですが、簡単にはいかないところがが素人には少々敷居が高いようい感じました。(よくできているのですけどねぇ。。。)
気を取り直して、次回は、カメラと接続していこうと思います。
また、よろしければ見ていただけるとありがたいです。

タイトルとURLをコピーしました