ブラウザからROS2のcmd_velをpublishして実機ロボットを操作する統合検証(rosbridge + roslibjs)

統合実証

概要(前提)

前回までの検証において、ROS2(Jazzy)環境から/cmd_velを用いて、WiFi経由で実機ロボット(Arduino UNO R4 WiFi + タミヤ車体)を制御できることを確認しました。

ROS2(Jazzy)からcmd_velで実機ロボットを制御する統合検証

本検証ではその続きとして、rosbridgeおよびroslibjsを用い、ブラウザから/cmd_velをpublishすることで、Webインターフェース経由で実機ロボットを操作できるかを確認しました。

このようなロボットを自作のROS2ノードとrosbridge_serverを稼働させた状態で以下のような画面から(htmlページ)コントロールします。


構成

本検証の構成は以下の通りです。

ブラウザ(HTML + roslibjs)
↓ WebSocket
rosbridge_server

ROS2(cmd_vel)

ROS2ノード(cmd_vel → コマンド変換)
↓ WiFi
Arduino UNO R4 WiFi

タミヤ車体

実施内容

使用した要素

本検証では以下を使用しました。

  • rosbridge_server
  • roslibjs(CDN)
  • シンプルなHTML操作画面(ボタンによる前後左右・停止)

rosbridge_serverの準備

rosbridgeの導入

rosbridgeはROS2環境から以下のコマンドでインストールできます。

sudo apt update
sudo apt install ros-jazzy-rosbridge-server

rosbridgeの起動

インストール後、以下のコマンドでWebSocketサーバーを起動します。

ros2 launch rosbridge_server rosbridge_websocket_launch.xml

起動すると、デフォルトでポート「9090」で待ち受けを行います。


接続先

ブラウザ側からは、以下の形式で接続します。

ws://<ROS2環境のIPアドレス>:9090

例:

ws://192.168.1.100:9090

■ 補足

  • rosbridgeはWebSocket経由でROS2と通信するためのブリッジです
  • roslibjsなどのJavaScriptライブラリから、トピックのpublish/subscribeが可能になります

HTML操作画面の用意

roslibjsを利用した簡易的なHTMLページを作成し、ボタン操作により/cmd_velをpublishできるようにしました。

操作としては以下を用意しています。

  • 前進
  • 後退
  • 左旋回
  • 右旋回
  • 停止

ここでは(AIを利用して生成したHTMLファイルですが、)以下のようなHTMLファイルを利用してテストをしました。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>ROS2 手動操作(cmd_vel)</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/roslibjs/1.1.0/roslib.min.js"></script>

<style>
  body {
    font-family: sans-serif;
    background: #f5f5f5;
    margin: 0;
    display: flex;
    flex-direction: column;
    height: 100vh;
  }

  .header {
    background: #333;
    color: white;
    padding: 10px;
  }

  .container {
    display: flex;
    flex: 1;
  }

  .left {
    flex: 1;
    display: flex;
    flex-direction: column;
    padding: 10px;
  }

  .controls {
    display: grid;
    grid-template-columns: repeat(3, 80px);
    gap: 10px;
    justify-content: center;
    margin-top: 20px;
  }

  button {
    padding: 15px;
    font-size: 18px;
    cursor: pointer;
    border-radius: 5px;
    border: none;
    background: #3498db;
    color: white;
  }

  button:hover {
    background: #2980b9;
  }

  .stop {
    background: #e74c3c;
  }

  .stop:hover {
    background: #c0392b;
  }

  .log {
    margin-top: 20px;
    background: #111;
    color: #0f0;
    padding: 10px;
    font-family: monospace;
    height: 200px;
    overflow-y: auto;
  }

  .status {
    margin-top: 10px;
  }
</style>
</head>

<body>

<div class="header">
  ROS2 手動操作(cmd_vel)
</div>

<div class="container">
  <div class="left">

    <div>
      ROSBridge URL:
      <input id="url" value="ws://192.168.xxx.xxx:9090" style="width:250px;">
      <button onclick="connect()">CONNECT</button>
    </div>

    <div class="status">
      Status: <span id="status">DISCONNECTED</span>
    </div>

    <div class="controls">
      <div></div>
      <button onclick="forward()">↑</button>
      <div></div>

      <button onclick="left()">←</button>
      <button class="stop" onclick="stopRobot()">■</button>
      <button onclick="right()">→</button>

      <div></div>
      <button onclick="back()">↓</button>
      <div></div>
    </div>

    <div class="log" id="log">
      --- LOG ---<br>
    </div>

  </div>
</div>

<script>
let ros = null;
let cmdVel = null;

function log(msg) {
  const area = document.getElementById("log");
  const time = new Date().toLocaleTimeString();
  area.innerHTML += `[${time}] ${msg}<br>`;
  area.scrollTop = area.scrollHeight;
}

function connect() {
  const url = document.getElementById("url").value;
  ros = new ROSLIB.Ros({ url: url });

  ros.on('connection', () => {
    document.getElementById("status").innerText = "CONNECTED";
    document.getElementById("status").style.color = "green";
    log("ROS 接続成功");

    cmdVel = new ROSLIB.Topic({
      ros: ros,
      name: '/cmd_vel',
      messageType: 'geometry_msgs/Twist'
    });
  });

  ros.on('error', (err) => {
    log("エラー: " + err);
  });

  ros.on('close', () => {
    document.getElementById("status").innerText = "CLOSED";
    document.getElementById("status").style.color = "red";
    log("接続切断");
  });
}

function send(linear, angular, label) {
  if (!cmdVel) {
    log("⚠ 未接続");
    return;
  }

  const msg = new ROSLIB.Message({
    linear: { x: linear, y: 0, z: 0 },
    angular: { x: 0, y: 0, z: angular }
  });

  cmdVel.publish(msg);
  log(label);
}

// --- 操作 ---
function forward() { send(1.0, 0.0, "前進"); }
function back()    { send(-1.0, 0.0, "後退"); }
function left()    { send(0.0, 1.0, "左回転"); }
function right()   { send(0.0, -1.0, "右回転"); }
function stopRobot(){ send(0.0, 0.0, "停止"); }
</script>

</body>
</html>

Webサーバーの起動

作成したHTMLファイルは、ローカル環境で簡易Webサーバーを起動して配信しました。

python3 -m http.server 8000

これにより、ブラウザからHTMLページへアクセス可能となります。


rosbridgeとの接続

HTMLページ上から、rosbridge_serverへWebSocket接続を行います。

接続先は以下の形式です。

ws://<ROS2環境のIPアドレス>:9090

接続後、roslibjsを通じて/cmd_velトピックへメッセージをpublishします。


検証結果

HTMLページからボタン操作を行うことで、

  • /cmd_velのpublish
  • ROS2ノードによる変換処理
  • Arduinoへの送信
  • 実機ロボットの動作

が一連の流れとして動作することを確認しました。

また、前回実装した超音波センサーによる距離検知も有効であり、一定距離(約15cm)での停止動作がWeb操作時にも機能することを確認しました。


技術的ポイント

  • rosbridgeを利用することで、ブラウザから直接ROS2へアクセス可能
  • roslibjsによりJavaScriptから簡単にトピック操作が可能
  • /cmd_velをインターフェースとして統一することで、操作手段(CLI / Web)の差異を吸収
  • センサー制御はロボット側で優先処理されるため、安全性を維持

位置づけ(ロボット実機の統合検証②)

本検証は以下の要素を統合した検証として位置付けます。

  • ROS2によるロボット制御
  • WebSocket通信(rosbridge)
  • ブラウザベースの操作インターフェース
  • 実機ロボットの駆動

これにより、コマンドライン操作に加えて、Webインターフェースからの操作が可能であることを確認しました。


今後の展開

本構成をベースとして、次の段階として以下を予定しています。

  • Blocklyによる動作フロー制御
  • 非エンジニアでも扱える操作環境の構築

これにより、操作インターフェースの抽象化と利用範囲の拡張を図ります。


まとめ

本検証により、rosbridgeおよびroslibjsを用いることで、ブラウザからROS2の/cmd_velをpublishし、実機ロボットを操作できることを確認しました。