Husarnetを使ってROS 2をインターネット越しに接続しM5ATOMのLEDを制御する

要素実証

はじめに

今回は、Husarnetを利用して離れた場所にあるROS 2環境を接続し、ROS 2のTopicを使って遠隔からデバイスを制御する要素検証を行います。Husarnetは、インターネット上にある複数のコンピュータを仮想的なネットワークで接続できる仕組みです。

ROS 2ではDDSを利用してノード間の通信を行いますが、通常のLAN内だけではなく、Husarnetを利用することでインターネットを経由したROS 2ノード間の通信を構成できます。

ここではHusarnetの登録方法、利用方法は割愛させて頂きます。既に環境が出来ている前提で進めます。もし必要な場合は以下の公式を参照してください。
https://husarnet.com/

今回の検証では、XServer VPS上のROS 2環境と手元のRaspberry PiをHusarnetで接続します。そして、XServer VPSからROS 2のTopicをPublishし、Raspberry PiでSubscribeします。さらにRaspberry PiからHTTP GETを送信し、同一LAN上にあるM5ATOM Liteの内蔵LEDを点灯させます。

単にROS 2のTopicが届くことを確認するだけではなく、最終的に実際のデバイスが動作するところまでを確認します。


今回の検証構成

今回の構成は次のようになります。

XServer VPSとRaspberry Piの間がHusarnetによる通信になります。
一方、Raspberry PiとM5ATOM Liteの間は通常のLAN内HTTP通信です。

つまりRaspberry Piは、

ROS 2 Topic → HTTP GET

を変換するゲートウェイとして動作します。

今回の要素検証では、この構成によってインターネット上のVPSから手元のLAN内にあるデバイスまで制御できることを確認します。


M5ATOM LiteのHTTP制御

M5ATOM Liteについては、前回の要素検証でWi-Fiに接続し、HTTP GETによって内蔵RGB LEDを制御できるところまで確認しています。※必要な場合は以下を参照して下さい。

この前回の投稿を簡単に説明しますと、

M5ATOM Lite上ではHTTPサーバーを動作させ、次のURLを用意しています。

/red
/yellow
/green
/off

例えば、

http://m5atom.local/red

へHTTP GETを送信するとLEDが赤色になります。

同様に、

http://m5atom.local/green

では緑色、

http://m5atom.local/off

ではLEDを消灯します。

今回はこの仕組みをそのまま利用します。

M5ATOM Lite側にROS 2やHusarnetを導入するのではなく、Raspberry PiでROS 2のTopicを受信し、それをHTTP GETへ変換します。

これによって、ROS 2に対応していないWi-FiデバイスもROS 2システムの先に接続できます。


ROS 2からHTTP GETを送信するノード

ここからは今回の内容です。
Raspberry Pi側には、ROS 2のTopicをSubscribeしてHTTP GETを送信するPythonノードを作成します。

今回は次のTopicを使用します。

/atom_led

メッセージ型は、

std_msgs/msg/String

です。

受信する文字列は次の4種類としました。

red
yellow
green
off

例えば、

red

を受信すると、

http://m5atom.local/red

へHTTP GETを送信します。

ノードの処理は非常に単純です。

/atom_ledをSubscribe
        ↓
文字列を取得
        ↓
red / yellow / green / off を判定
        ↓
対応するURLを生成
        ↓
HTTP GET
        ↓
M5ATOM LiteのLEDを制御

今回はROS 2とHTTPの接続確認が目的なので、できるだけ単純な構成としています。


ROS 2パッケージの作成

Raspberry PiのROS 2ワークスペースでPythonパッケージを作成します。

cd ~/ros2_ws/src

ros2 pkg create atom_http_control \
  --build-type ament_python \
  --dependencies rclpy std_msgs

今回はパッケージ名を、

atom_http_control

としました。

ノード名は、

atom_http_node

としています。


HTTP制御ノード

作成したPythonノードでは、/atom_ledをSubscribeします。

self.subscription = self.create_subscription(
    String,
    '/atom_led',
    self.led_callback,
    10
)

Topicを受信すると、受信した文字列からURLを生成します。

command = msg.data.strip().lower()

url = f'{self.base_url}/{command}'

HTTP GETにはPython標準ライブラリのurllib.requestを使用しました。

with urllib.request.urlopen(
    url,
    timeout=3.0
) as response:

    body = (
        response.read()
        .decode('utf-8')
        .strip()
    )

今回はHTTP通信のために追加ライブラリを導入せず、Pythonの標準機能だけを使用しています。
以下がプログラム全体です。

#!/usr/bin/env python3

import urllib.request
import urllib.error

import rclpy
from rclpy.node import Node
from std_msgs.msg import String


class AtomHttpNode(Node):

    def __init__(self):
        super().__init__('atom_http_node')

        self.declare_parameter(
            'base_url',
            'http://m5atom.local'
        )

        self.base_url = (
            self.get_parameter('base_url')
            .get_parameter_value()
            .string_value
        )

        self.subscription = self.create_subscription(
            String,
            '/atom_led',
            self.led_callback,
            10
        )

        self.get_logger().info('ATOM HTTP control node started')
        self.get_logger().info(f'base_url = {self.base_url}')
        self.get_logger().info('Waiting for /atom_led ...')


    def led_callback(self, msg):

        command = msg.data.strip().lower()

        valid_commands = [
            'red',
            'yellow',
            'green',
            'off'
        ]

        if command not in valid_commands:
            self.get_logger().warning(
                f'Unknown command: {command}'
            )
            return

        url = f'{self.base_url}/{command}'

        self.get_logger().info(
            f'HTTP GET: {url}'
        )

        try:
            with urllib.request.urlopen(
                url,
                timeout=3.0
            ) as response:

                body = (
                    response.read()
                    .decode('utf-8')
                    .strip()
                )

                self.get_logger().info(
                    f'HTTP response: {body}'
                )

        except urllib.error.URLError as e:
            self.get_logger().error(
                f'HTTP request failed: {e}'
            )

        except Exception as e:
            self.get_logger().error(
                f'Unexpected error: {e}'
            )


def main(args=None):

    rclpy.init(args=args)

    node = AtomHttpNode()

    try:
        rclpy.spin(node)

    except KeyboardInterrupt:
        pass

    node.destroy_node()
    rclpy.shutdown()


if __name__ == '__main__':
    main()

まずRaspberry Pi内で動作確認

いきなりXServer VPSから操作するのではなく、最初にRaspberry Pi内だけで動作確認を行いました。

まずHTTP制御ノードを起動します。

ros2 run atom_http_control atom_http_node

ノードが起動すると、

ATOM HTTP control node started
base_url = http://m5atom.local
Waiting for /atom_led ...

と表示され、/atom_ledの受信待ちになります。以下がそのスクリーンショットです。

別のターミナルから次のTopicをPublishします。

ros2 topic pub --once \
  /atom_led \
  std_msgs/msg/String \
  "{data: 'red'}"

するとatom_http_nodeがredを受信し、

http://m5atom.local/red

へHTTP GETを送信します。以下がそのスクリーンショットです。

M5ATOM Liteの内蔵LEDが赤色に点灯することを確認しました。

同様に、

ros2 topic pub --once \
  /atom_led \
  std_msgs/msg/String \
  "{data: 'yellow'}"
ros2 topic pub --once \
  /atom_led \
  std_msgs/msg/String \
  "{data: 'green'}"
ros2 topic pub --once \
  /atom_led \
  std_msgs/msg/String \
  "{data: 'off'}"

を実行し、黄色、緑色、消灯についても正常に制御できることを確認しました。

これによって、

ROS 2 Topic
    ↓
Raspberry Pi
    ↓
HTTP GET
    ↓
M5ATOM Lite

の部分が正常に動作していることを確認できました。


XServer VPSからHusarnet経由で制御

ローカルでの動作確認ができたので、次にPublishするROS 2環境をXServer VPSへ移します。

XServer VPSとRaspberry Piは、あらかじめHusarnetを利用してROS 2 Humble同士で通信できる状態にしています。

Raspberry Pi側では先ほどと同じatom_http_nodeを起動した状態にします。

そしてXServer VPS側から、

ros2 topic pub --once \
  /atom_led \
  std_msgs/msg/String \
  "{data: 'red'}"

を実行します。

XServer VPSからPublishされた/atom_ledはHusarnetを経由してRaspberry Piへ到達します。

Raspberry Pi上のatom_http_nodeがTopicをSubscribeすると、M5ATOM LiteへHTTP GETを送信します。

結果として、XServer VPSからの操作によってM5ATOM LiteのLEDを点灯させることができました。黄色、緑色、消灯についても同様に動作することを確認しました。
以下はgreenのスクリーンショットです。


今回確認できたこと

今回の要素検証では、次の通信経路が実際に動作することを確認しました。

XServer VPS
ROS 2 Humble
      │
      │ /atom_led
      ▼
   Husarnet
      │
      ▼
Raspberry Pi 4
ROS 2 Humble
      │
      │ atom_http_node
      ▼
   HTTP GET
      │
      ▼
M5ATOM Lite
      │
      ▼
    RGB LED

まずRaspberry Pi内でROS 2 TopicからHTTP GETへの変換を確認し、その後XServer VPSから同じTopicをPublishすることで、Husarnetを経由した場合にも同じ動作ができることを確認しました。

これによって、Husarnetを利用してインターネット上のROS 2環境と手元のROS 2環境を接続し、さらにその先にあるLAN内のデバイスまで制御できることが確認できました。

今回は動作が分かりやすいようにM5ATOM LiteのLEDを使用しましたが、Raspberry Pi側でHTTP通信を行う構成自体は、他のWi-Fiデバイスや制御システムにも応用できます。

また、最終的な制御対象をROS 2対応機器だけに限定せず、

ROS 2
 ↓
HTTP
 ↓
既存のネットワーク機器

という構成を取れることも今回の検証で確認できました。

まとめ

今回は、Husarnetを利用してXServer VPSとRaspberry PiのROS 2 Humble環境を接続し、ROS 2 Topicをインターネット越しに送信する要素検証を行いました。

さらにRaspberry Pi上でROS 2 TopicをHTTP GETへ変換するノードを作成し、同一LAN上のM5ATOM Liteを制御しました。

XServer VPSからROS 2 TopicをPublishすることで、手元にあるM5ATOM LiteのLEDを赤、黄、緑に点灯させ、消灯させるところまで確認できました。

今回の検証によって、

インターネット → Husarnet → ROS 2 → Raspberry Pi → HTTP → Wi-Fiデバイス

という一連の通信経路が実際に利用できることを確認できました。

今後はこの構成をベースとして、LED制御だけではなく、ロボットの状態取得や遠隔操作などへの応用も検討していきます。