6
5

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 3 years have passed since last update.

ROS講座47 GamePadをブラウザで使う

Last updated at Posted at 2019-05-20

環境

この記事は以下の環境で動いています。

項目
CPU Core i5-8250U
Ubuntu 16.04
ROS Kinetic

インストールについてはROS講座02 インストールを参照してください。
またこの記事のプログラムはgithubにアップロードされています。ROS講座11 gitリポジトリを参照してください。

概要

ロボットをリモートから監視、操作するときにWeb経由はうってつけの物です。Webの技術は出力(表示)系は充実していますが、入力系は十分とは言えません。そこでGamepadAPIを使ってGamePadの入力値をWeb経由でROSに送ります。

GamePad APIのサンプル

ソースコード

Gamepad APIを使うhtmlのサンプルです。

web_lecture/www/gamepad_api_test.html
<!doctype html>
<html lang="ja">
  <head>
    <title>Gamepad DEmo</title>
    <meta charset="UTF-8">
  </head>
    <body>
      <div id="disp">test</div>
      <script>

//Create AnimationFrame
var rAF = window.mozRequestAnimationFrame ||
    window.webkitRequestAnimationFrame ||
    window.requestAnimationFrame; 

//Update
function update() {
  var pads = navigator.getGamepads ? navigator.getGamepads() :
    (navigator.webkitGetGamepads ? navigator.webkitGetGamepads : []);

  //tekitou
  pads = pads[0];
	if(pads) {
		//buttons
		var but = [];
		for(var i = 0 ; i < pads.buttons.length; i++) {
			var val = pads.buttons[i];
			var pressed = val == 1.0;
			if (typeof(val) == "object") {
				pressed = val.pressed;
				val     = val.value;
			}
			but[i] = val;
		}

		var txt = [];
		txt += pads.id + "<br>";
		for(var i = 0 ; i < but.length; i++) {
			if( but[i] == 1)  txt += '<input type="checkbox" checked="checked">';
			else              txt += '<input type="checkbox" >';
		}

		var axes = pads.axes;
		for(var i = 0 ; i < axes.length; i++) {
			txt += '<br>';
			txt += axes[i] ;
		}
		console.log(axes);
		document.getElementById("disp").innerHTML = txt;
	}
}

//Start
setInterval("update()",100);

    </script>
  </body>
</html>

実行

GamePadをPCに挿してこのページを開くと以下のように見えます。ボタンを押したかや軸の値が見えます。

web_gamepad_api.png

Web経由でGamePadをROSに接続する

ソースコード

GamePad APIで取得したGamePadのデータをWebSocketを使ってROSに流します。

web_lecture/www/gamepad.html
<!doctype html>
<html lang="ja">
  <head>
    <title>ROS UI</title>
    <meta charset="UTF-8">
    <style>
    *{
      margin: 0px;
      padding: 0px;
      border: 0px;
    }
    .full_picture{
      width: 100vw;
      height: 100vh;
      object-fit: contain;
    }
    </style>
    <script src="https://static.robotwebtools.org/EventEmitter2/current/eventemitter2.min.js"></script>
    <script src="https://static.robotwebtools.org/roslibjs/current/roslib.min.js"></script>
  </head>
  
  <body>
    <script>
      var topic_name = "/web/image_raw";
      document.write("<img class='full_picture' src='http://"+location.hostname +":8080/stream?topic=" + topic_name + "&type=ros_compressed'></img>");
    </script>
    <p>RosBridge status: <label id="rosbridge_status">Disconnect</label></p>
    <p>Gamepad status:   <label id="gamepad_status">Disconnect</label></p>
    <p>Gamepad buttons:   <div id="gamepad_buttons">None</div></p>
    <p>Gamepad axes:   <div id="gamepad_axes">None</div></p>
  </body>

  <script>
var Talker = {
  ros : null,
  name : "",
  init : function(){
    this.ros = new ROSLIB.Ros();
    this.ros.on('error', function(error) {
        document.getElementById('rosbridge_status').innerHTML = "Error";
    });
    this.ros.on('connection', function(error) {
        document.getElementById('rosbridge_status').innerHTML = "Connect";
    });
    this.ros.on('close', function(error) {
        document.getElementById('rosbridge_status').innerHTML = "Close";
    });
    this.ros.connect('ws://' + location.hostname + ':9090');
  },
  send : function(joy_data){
    if(joy_data != null){
      var pub = new ROSLIB.Topic({
          ros : this.ros,
          name : '/web/joy',
          messageType : 'sensor_msgs/Joy'
      });
      pub.publish(joy_data);
    }
  },
  getJoy : function() {
    var pads = navigator.getGamepads ? navigator.getGamepads() :
                    (navigator.webkitGetGamepads ? navigator.webkitGetGamepads : []);
    
    var joy_data = new ROSLIB.Message({  axes:[], buttons:[]});
    pad = pads[0];
    if(pad) {
      document.getElementById("gamepad_status").innerHTML = "Connected";

      //buttons for display
      var but = "";
      for(var i = 0 ; i < pad.buttons.length; i++) {
        but += "but" + i + ":" + pad.buttons[i].value + "<br>";//or .pressed
      }
      document.getElementById("gamepad_buttons").innerHTML = but;
      
      //axes for display
      var ax = "";
      for(var i = 0 ; i < pad.axes.length; i++) {
        ax += pad.axes[i] + '<br>';
      }
      document.getElementById("gamepad_axes").innerHTML = ax;

      //for json
      for(var i = 0 ; i < pad.buttons.length; i++) {
        joy_data.buttons.push(Number(pad.buttons[i].pressed));      
      }
      for(var i = 0 ; i < pad.axes.length; i++) {
        joy_data.axes.push(pad.axes[i]);
      }    
      for(var i = 0 ; i < pad.buttons.length; i++) {
        joy_data.axes.push(pad.buttons[i].value);      
      }
      console.log(joy_data);
      return joy_data
    }
    else{
      document.getElementById("gamepad_status").innerHTML = "Disonnected";
      joy_data = null
    }
  }
}
Talker.init();

window.onload = function(){
};
window.onunload = function(){
  Talker.ros.close();
};

function update(){
  joy_data = Talker.getJoy();
  Talker.send(joy_data);
}
setInterval("update()",100);
  </script>
</html>

gazeboでcameraとtwistでの移動機構をspawnします。

web_lecture/launch/web_if.launch
<launch>
  <arg name="model" default="$(find web_lecture)/xacro/odm_simple.xacro" />
  <param name="robot_description" command="$(find xacro)/xacro.py $(arg model)" />

  <include file="$(find roswww)/launch/roswww.launch" />
  <node pkg="web_video_server" type="web_video_server" name="web_video_server" />
  <include file="$(find rosbridge_server)/launch/rosbridge_websocket.launch" />

  <include file="$(find gazebo_ros)/launch/empty_world.launch">
    <arg name="paused" value="false"/>
    <arg name="use_sim_time" value="true"/>
    <arg name="gui" value="true"/>
    <arg name="headless" value="false"/>
    <arg name="debug" value="false"/>
  </include>

  <node name="spawn_urdf" pkg="gazebo_ros" type="spawn_model" args="-param robot_description -urdf -model odm_robot" />

  <node name="image_republish" pkg="image_transport" type="republish" args="compressed compressed">
    <remap from="in" to="/head_camera/image_raw" />
    <remap from="out" to="/web/image_raw" />
  </node>
  <node name="twist_publisher" pkg="web_lecture" type="twist_publisher.py" />
</launch>

実行

roslaunch web_lecture web_if.launch 

GamePadをPCに接続した後にブラウザでhttp://localhost:8085/web_lecture/gamepad.htmlを開きます。GamePadを操作することでシミュレーター中のロボットが動きます。

web_gamepad.png

実行

スマホから使う

今回のWeb系統の技術はROSの動作しているPCからの通信ではなく、他の端末からの通信で真価を発揮します。今回はスマホにGamePadを使って上記の通信をやってみます。

iphone_connect.png

iphoneではiOS12以降のsafariでGamepadAPIが対応しているようです。またiphoneではMFIという認証を取っているGamepadしか使えないようです。以下の構成で今回のプログラムが動作することを確認しました。手元で試したところGamepadAPIでの接続が多少不安定な感じがあります。Gamepadを開く->safariで上記のページを開く->画面を何回かタッチする。といった作業が必要です。たまにつながらないときがありますが、その時はiphoneの再起動をするとつながるようになります。

項目
端末 iphone6s
OS ios12
ブラウザ safari
GamePad PXN-6603

iPhoneJoy.JPG

参考

GamePad APIについて

目次ページへのリンク

ROS講座の目次へのリンク

6
5
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
6
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?