環境
この記事は以下の環境で動いています。
項目 | 値 |
---|---|
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のサンプルです。
<!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に流します。
<!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します。
<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系統の技術はROSの動作しているPCからの通信ではなく、他の端末からの通信で真価を発揮します。今回はスマホにGamePadを使って上記の通信をやってみます。
iphoneではiOS12以降のsafariでGamepadAPIが対応しているようです。またiphoneではMFIという認証を取っているGamepadしか使えないようです。以下の構成で今回のプログラムが動作することを確認しました。手元で試したところGamepadAPIでの接続が多少不安定な感じがあります。Gamepadを開く->safariで上記のページを開く->画面を何回かタッチする。といった作業が必要です。たまにつながらないときがありますが、その時はiphoneの再起動をするとつながるようになります。
項目 | 値 |
---|---|
端末 | iphone6s |
OS | ios12 |
ブラウザ | safari |
GamePad | PXN-6603 |