Edited at

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


環境

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

項目

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を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に接続した後にブラウザを開きます。GamePadを操作することでシミュレーター中のロボットが動きます。


実行


スマホから使う

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

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

項目

端末
iphone6s

OS
ios12

ブラウザ
safari

GamePad
PXN-6603


参考

GamePad APIについて


目次ページへのリンク

ROS講座の目次へのリンク