ROSのWebインタフェースを作る

　ロボットをWebブラウザから色々操作できれば，便利なわけです．
　個人的にも，人の入力を待つみたいな非同期イベントは，ロボット制御のループからは離しておきたいので，そういう意味でも非同期入力が得意なWebブラウザにお任せしておくのは良さそうだと思っています．

#Webサーバを立ち上げる
　なんとびっくりなんですが，ROSのパッケージの中に，簡単なWebサーバを実現するものがありました！[roswww](http://wiki.ros.org/roswww)．
　こいつをインストールすれば，最低限の機能は実現できそうです．

```sh:apt
sudo apt-get install ros-indigo-roswww
```

　実際に起動してみましょう．

```sh:roswww.launch
roslaunch roswww roswww.launch
```

　この状態で， http://localhost:8085/ にアクセスすると，インストール済みパッケージのリストを表示するページが開きます．また， http://localhost:8085/roswww/ にアクセスすると，「roswww, sup?」とだけ書かれたテストページが開きます．
　roswwwは， *rosインストール先/share/各パッケージ/www/* のフォルダを参照するので，自作のパッケージにも *www* フォルダを作って， *share* 下にインストールすれば， http://localhost:8085/自作パッケージ名/ というURLでアクセスできるようになります．

#JavaScriptでTopicをPub/Subする
　さて，静的なページだけだと，やはりつまらない．Webページ上からTopicを投げる方法がないかros-jpのメーリングリストで質問したら，[roslibjs](http://wiki.ros.org/roslibjs)という，javascriptから直接ROS TopicのPub/SubやActionの発行など，ROSの緒機能にアクセスできるパッケージを紹介してもらいました！このパッケージは，[rosbridge](http://wiki.ros.org/rosbridge_suite)で提供されるROSのwebsocketの口を利用するようです．
　さっそくインストール．

```sh:apt
sudo apt-get install ros-indigo-rosbridge-server
```

（もしかしたら，roswwwの依存関係で，もう入ってるかも）

　んで，roswwwのlaunchフォルダに以下のようなlaunchファイルを書いて，

```xml:chat.launch
<launch>
  <include file="$(find rosbridge_server)/launch/rosbridge_websocket.launch" />
  <include file="$(find roswww)/launch/roswww.launch" />
</launch>
```

同じくroswwwのwwwフォルダに以下のようなhtmlファイルを入れます．

```html:chat.html
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
	<head>
		<meta http-equiv="content-type" content="text/html;charset=utf-8"/>
		<style type="text/css">
		<!--
			*{ padding:0px; margin:0px;}
		-->
		</style>
		<title>Sample Chat</title>
		<script src="http://cdn.robotwebtools.org/EventEmitter2/current/eventemitter2.js"></script>
		<script src="http://cdn.robotwebtools.org/roslibjs/current/roslib.js"></script>	</head>
	<body>
		<script type="text/javascript">
		<!--
if(!Chat){
	var Chat = {
		ros : null,
		name : "",
		init : function(){
			this.ros = new ROSLIB.Ros();
			this.ros.on('error', function(error) {
				document.getElementById('state').innerHTML = "Error";
				document.getElementById("btn").disabled = true;
			});
			this.ros.on('connection', function(error) {
				document.getElementById('state').innerHTML = "Connect";
				document.getElementById("btn").disabled = false;
			});
			this.ros.on('close', function(error) {
				document.getElementById('state').innerHTML = "Close";
				document.getElementById("btn").disabled = true;
			});
			this.ros.connect('ws://localhost:9090');

			var sub = new ROSLIB.Topic({
				ros : this.ros,
				name : '/chat',
				messageType : 'std_msgs/String'
			});
			sub.subscribe(function(message) {
				var res = message.data;
				var date = res.substring(1, res.indexOf("]"));
				res = res.substring(res.indexOf("]")+1, res.length);
				var name = res.substring(0, res.indexOf(":"));
				res = res.substring(res.indexOf(":")+1, res.length);
				var el = document.createElement("p");
				el.innerHTML = "[" + date + "] <b>" + name + " :</b> " + res;
				//document.getElementById("talk").appendChild(el);
				document.getElementById("talk").insertBefore(el, document.getElementById("talk").childNodes[0]);

				document.getElementById("btn").disabled = false;
				document.getElementById("state").innerHTML = "received";
			});
		},
		send : function(){
			if(document.getElementById("comment").value.length == 0) return;

			document.getElementById("btn").disabled = true;
			document.getElementById("state").innerHTML = "sending";

			var now = new Date();
			var str_date = (now.getMonth()<10?"0":"") + (now.getMonth()+1) + "/";
			str_date += (now.getDate()<10?"0":"") + now.getDate() + "/";
			str_date += now.getFullYear() + " ";
			str_date += (now.getHours()<10?"0":"") + now.getHours() + ":";
			str_date += (now.getMinutes()<10?"0":"") + now.getMinutes() + ":";
			str_date += (now.getSeconds()<10?"0":"") + now.getSeconds(); 

			var comment = document.getElementById("comment").value;
			var pub = new ROSLIB.Topic({
				ros : this.ros,
				name : '/chat',
				messageType : 'std_msgs/String'
			});

			var str = new ROSLIB.Message({data : "[" + str_date + "]" + this.name + ":" + comment});
			pub.publish(str);
			document.getElementById("comment").value = "";
		}
	}
	Chat.init();

	window.onload = function(){
		Chat.name = window.prompt("What's your name?", "Jane Doe");
		document.getElementById("name").innerHTML = Chat.name;
	};
	window.onunload = function(){
		Chat.ros.close();
	};
}
		//-->
		</script>
<p>Connection status： <label id="state">Disconnect</label></p>
<p>Your name： <label id="name"></label></p>
<form action="javascript:Chat.send();" id="ui">
<label>Comment: </label>
<input type="text" id="comment" size="20" />
<input type="submit" value="send" id="btn" /><br/>
</form>
<div id="talk"><p></p></div>

	</body>
</html>
```

　それでは，いつものようにインストール．私はros_make_isolatedを愛用してます．ros_makeでも，たぶん同じ．

```sh:catkin_make
catkin_make_isolated --install --pkg roswww
```

そして，以下のコマンドで起動します．

```sh:launch_roswww
roslaunch roswww chat.launch
```

　ここまでできたら，ブラウザで http://localhost:8085/roswww/chat.html にアクセスしてください．名前の入力を促すダイアログが現れるので，適当に入力してください．
　次に，さらにもう１つ別のウィンドウで同じページにアクセスしてください．この状態で，片方のウィンドウからコメントを送ると，rostopicを介して，もう片方のウィンドウにも表示されます！

![ROSでChat](https://pbs.twimg.com/media/CWHe_9jUsAE7VQu.png)

　プロジェクトの全文は[GitHub](https://github.com/yoneken/roswww)にも置いてあるので，参考にしてくださいー．

　以上，WebブラウザをROSのインタフェースにするお話しでしたっ！

<!--
#（応用編）CGIにして外部に公開する
　ローカルのROSにTopicを投げるだけなら以上の手順だけで良いですが，外部のPCやスマートフォンからも操作したい場合は，CGIにしてPCのIPアドレスを明示的に指示する必要があると思います．
##/etc/apache2/apache2.conf(httpd.conf)を編集
・Optionsに **ExecCGI** を追加
・AddHandler cgi-scriptに **.py** を追加
-->