ハッカソンなどでデベロッパーの方をサポートしていた際、サーバー上の Web コンテンツを Pepper のタブレットにインテグレーションされようとして苦労されている場面を何度かみました。いわゆるクロスドメインの問題によりサーバーから Pepper のタブレットに読み込んだ HTML ページと Pepperは qimessaging で直接通信をすることはできません。 「iframe とか使えばできると思うんですが、、」とお伝えしていたわけですが、実際動くサンプルを作って見ました。なるべく使いわましがきくように作ってみたので、色々なシーンで利用してみていただけると幸いです。
#対象
これは、Pepper アプリ開発の中上級者向けの記事です。 qimessaging によるタブレットとの連携方法などがご存知で、HTML、JavaScript について基礎的な知識を持っていることを前提としています。
#概要
タブレット上の HTML コンテンツと Choregraphe のアプリを連携させるには qimessaging JavaScript が有効ですが、ここで連携する HTML コンテンツは基本 Choregraphe のアプリのプロジェクトの中に含まれている必要があります。JavaScript がセキュリティー保護のためオリジンサーバー以外とは通信をさせないクロスドメインの制限を持っているためです。
ここではこの問題に対処して、リモートサーバー上のコンテンツと Choregraphe で作られるロボアプリが直接メッセージ交換をできるようにする方法を案内します。 具体的には Choregraphe プロジェクトが持つ HTML コンテンツが iframe で遠隔のコンテンツを読み込み、双方が postMessage でメッセージ交換することにより、遠隔コンテンツとの連携を実現します。
ちなみにここでは扱いませんが、逆のパターンで Choregraphe プロジェクト内で用意した HTML ページが XMLHttpRequest などを使ってリモートリソースにアクセスしたい場合があります。 この場合もクロスドメイン問題に対処する必要がありますが、この場合はリモートサーバー側に Cross-origin resource sharing (CORS) 対策を行うというのが常套手段かと思います。
#Choregraphe プロジェクト内の HTML コンテンツ
Choregaraphe プロジェクトで用意する HTML コンテンツは iframe を持ちます。また qimessaging JavaScript を読み込み、アプリと通信、さらに iframe と postMessage を使って通信をします。
次がサンプルコードです。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=1280, initial-scale=1.34" />
<script src="/libs/qimessaging/2/qimessaging.js"></script>
<style type="text/css">
<!--
body {
margin: 0px;
}
#remoteFrame {
width:1280px;
height:800px;
margin:0px;
border:0px;
}
-->
</style>
<script type="text/javascript">
//ここを編集
var remoteDomain = "http://<サーバー側のドメイン 例 http://myserver.com >";
var remotePage = "<表示する html ページ 例 /server.html>";
//ここを編集終了
var _remoteFrame = null;
var _alsession = null;
var _almemory = null;
function toTabletHandler(value) {
console.log("PepperQiMessaging/toRemoteFrameイベント発生: " + value);
sendMessageToRemoteContent(value);
}
function sendMessageToRemoteContent(data) {
_remoteFrame.contentWindow.postMessage(data, remoteDomain);
};
function receiveMessageFromRemoteContent(event) {
// 引数のオリジンの URL のチェック
if (event.origin !== remoteDomain) {
console.warn('INVALID ORIGIN');
return;
}
if (_almemory) {
_almemory.raiseEvent("PepperQiMessaging/fromRemoteFrame", event.data).then(function() {
// コールバック
});
} else {
_session.service("ALMemory").then(function (almemory) {
console.log("ALMemory取得成功");
_almemory = almemory;
ALMemory.subscriber("PepperQiMessaging/toRemoteFrame").then(function(subscriber) {
_almemory.raiseEvent("PepperQiMessaging/fromRemoteFrame", event.data).then(function() {
// コールバック
});
});
});
}
};
function init()
{
QiSession(function( session ) {
_alsession = session;
session.service("ALMemory").then(function (almemory) {
_almemory = almemory;
console.log("ALMemory取得成功");
_almemory.subscriber("PepperQiMessaging/toRemoteFrame").then(function(subscriber) {
subscriber.signal.connect(toTabletHandler);
});
});
}, null, null);
window.addEventListener('message', receiveMessageFromRemoteContent, false);
_remoteFrame = document.getElementById('remoteFrame');
_remoteFrame.src = remoteDomain + remotePage;
}
</script>
<title>外部サーバーコンテンツとの通信サンプル</title>
</head>
<body onLoad="init();">
<iframe id="remoteFrame"></iframe>
</body>
</html>
これはラッパー的なページのため、汎用性を持たせています。
コード内の
var remoteDomain = "http://<サーバー側のドメイン 例 http://myserver.com >";
var remotePage = "<表示する html ページ 例 /server.html>";
の部分を書き換えることで、そのリモートページを iframe 内に読み込み、表示してくれます。
また、アプリとの通信は、ALMemory のメモリーキー PepperQiMessaging/toRemoteFrame を通してアプリからリモートコンテンツへ、PepperQiMessaging/fromRemoteFrame を通して、リモートコンテンツからアプリへメッセージが伝播されるようにしています。
#遠隔サーバー側
遠隔サーバーのコンテンツが Pepper と通信をするためには、遠隔サーバー側にも通信をするための処理が必要、これを今回 JavaScript の postMessage を使って行うわけです。
次がそのサンプルです。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<script type="text/javascript">
// Pepper にメッセージを送る
function sendMessageToPepper(message) {
window.parent.postMessage(message, "http://198.18.0.1");
};
function init() {
window.addEventListener('message', receiveMessageFromPepper, false);
}
// Pepper からのメッセージを処理する
function receiveMessageFromPepper(event) {
if (event.origin !== "http://198.18.0.1") {
console.warn('INVALID ORIGIN');
return;
}
// この後に処理を書く event.data が Pepper からのデータ(文字列)
alert(event.data);
};
</script>
</head>
<body onLoad="init();">
<div style="font-size: 6em">
サーバー上のコンテンツ
</div>
<div>
<form name="form1">
<input style="font-size: 4em" type="text" name="form1_text" value="こんにちは">
<button style="font-size: 4em" type="button" onclick="sendMessageToPepper(document.form1.form1_text.value);">Pepperに送る</button>
</form>
</div>
</body>
</html>
このサンプルでは sendMessageToPepper() 関数を使って、Choregraphe アプリ側にメッセージを、また、receiveMessageFromPepper() 関数は、アプリからメッセージが発火された場合に呼ばれるので、ここにメッセージ処理を実装します。ちなみに オリジンサーバーが http://198.18.0.1 となっているのは、Pepper と Pepper のタブレットはインターナルの USB ネットワークでつながっており、このネットワークにおける、Pepper 側の IP アドレスによるものです。Chorgraphe プロジェクト内の HTML リソースを表示する際、タブレットはこの IP アドレスを介してPepperの本体頭脳部分とつながり、そこで動いている Webサーバーからリソースをロードしてきています。一見 Choregraphe プロジェクト内に配置されたローカルのコンテンツは localhost (127.0.0.1) がオリジンサーバーになるのかと思ってしまいがちなのですがそうではないところがちょっとした押さえておくべき知識となります。
#サンプル
上記サンプルコードと、実際に動作するサンプルアプリを Github に登録しました。
参考にしていただけると幸いです。なお、サーバーは暫定的に稼働させていますが、そのうち止めてしまうかもしれません。その時はごめんなさい。
Github の URL は次になります。
https://github.com/tkawata1025/pepper-remote-HTML-qimessaging-sample
#最後に
Pepper は開発環境が非常にオープンで拡張性が高い一方、セキュアーな情報の管理が少し難しいといえます。
サーバーサイドで情報を管理し、またタブレット側の localStorage などを使えば上手に管理ができるようになるのではと妄想しています。この辺りもう少し研究してみる予定です。