Pepperチュートリアル (5)：タブレットを使う

#このチュートリアルの内容

このチュートリアルでは、Pepperの特徴である胸のタブレットの制御方法として、tabletボックスライブラリで提供されるボックスの使い方について説明します。

1. タブレットの仕様
2. プロジェクトの内容 パネル
3. ボックスの使い方：コンテンツを表示する
4. ボックスの使い方：タッチイベントを取得する

なお、**バーチャルロボットではタブレットサービスはサポートされておらず、エラーとなります**が、今回のチュートリアルでは、タブレット関連ボックスで何をしているかの解析のため、Pythonで書かれたボックスの中身を見る方法なども説明していますので、関心がある方はぜひどうぞ。

#タブレットの仕様

Pepperのタブレットの仕様のうち主なものは以下のようになっています。

|項目|内容|
|---|-----------------------------|
| ディスプレイ|10.1インチ|
| 解像度	|1280 x 800|
| メディアファイル形式	|AVI, WMV, ASF, MP4, MKV, MPG, DAT, TS, TP, TRP, 3GP|
| ビデオコーデック	|DivX, XviD, H.264, WMV 9/8/7, MPEG1|
| ビデオ解像度	|Max 1920x1080|
| オーディオコーデック	|MPEG1 Layer 1/2/3, WMA, OGG Vorbis, PCM, FLAC|

画像や動画を表示したりといった出力のほか、タッチイベントを取得して振る舞いに反映するといったことが可能です。

#プロジェクトの内容 パネル

タブレットにコンテンツを表示するためには、表示したいコンテンツをプロジェクト内にあらかじめインポートしておく必要があります。
この時、**プロジェクトの内容**パネルの理解が必要になるため、ここで詳しく説明しておきます。

![project-content.png](https://qiita-image-store.s3.amazonaws.com/0/28770/8eab691a-9411-f61e-2417-633ed16e1dae.png)

1. プロジェクトファイル ... プロジェクトに含まれるファイルです。ディレクトリによって階層化でき、ファイルはドラッグによって移動できます
2. ファイル/ディレクトリの追加 ... プロジェクトファイルに要素を追加します。以下のメニューがあらわれます
     * ディレクトリの作成...
     * 動作を作成中...
     * ダイアログのトピックを作成中...
     * 平面動作を作成中...
     * 動作をインポート中...
     * ファイルのインポート...
     * フォルダーのインポート...
3. 選択したファイル/ディレクトリの削除

タブレットでのコンテンツ出力においては、画像、映像などともに **プロジェクトファイルに html ディレクトリを用意** し、そこに表示させたいファイルなどを配置することになります。また、これらのファイルを参照するボックス側では、パスのうち、**html ディレクトリを起点にしたパス** でファイルを指定することになります。操作方法の詳細はチュートリアルで説明していきます。

#ボックスの使い方：コンテンツを表示する

##画像の出力

まずは簡単に、画像をPepperのタブレットに表示させてみます。
PNG, JPEG, GIFのいずれかの形式の適当な画像ファイルを準備して作業をおこなってください。

###つくってみる

1. 利用するボックスの準備 ... tabletタブのボックスを使います
    * tablet > Show Image
2. ボックスをつなぐ
    ![show-image.png](https://qiita-image-store.s3.amazonaws.com/0/28770/1b8bd3e4-6366-259a-8998-f90c991b528d.png)
    * アプリケーションのonStartとともに、Show Imageボックスでタブレットに画像を表示するだけです

3. タブレット用にhtmlディレクトリを作成する
    プロジェクトの内容パネルの追加ボタンをクリックし、**[ディレクトリの作成...]** をクリックします
    ![create-html-directory.png](https://qiita-image-store.s3.amazonaws.com/0/28770/3ec1d660-1594-a332-46db-ada9473bc790.png)
    名前はhtmlとして作成すると、プロジェクトファイルとしてhtmlディレクトリが作成されます
    ![new-html-directory.png](https://qiita-image-store.s3.amazonaws.com/0/28770/0b0a97c0-c92b-d879-7319-f1ba65e4e9a7.png)

4. 画像をインポートする
    **[ファイルのインポート...]** をクリックすると、ファイル選択ダイアログが開きます。表示させたい画像ファイルを選択するとプロジェクトファイルとして画像ファイルが登録されます。
    ![add-image.png](https://qiita-image-store.s3.amazonaws.com/0/28770/b47f0193-ce06-995b-a8b0-22f8875ca5b0.png)

5. 画像ファイルをドラッグし、htmlディレクトリ内に移動します
    ![move-image.png](https://qiita-image-store.s3.amazonaws.com/0/28770/e20dd06c-d9e7-1551-f3e8-20014f94d4f0.png)
    これで画像ファイルがタブレットで表示可能な状態になりました。

6. Show Imageボックスのパラメータボタンをクリックし、ImageUrlに画像ファイル名を入力する
    ![show-image-params.png](https://qiita-image-store.s3.amazonaws.com/0/28770/4a31d17b-2b2b-5c1e-27aa-fa6dd2687023.png)
    この値は htmlディレクトリを起点としたパス表現になります。

7. **[おまじない]** ビヘイビアをディレクトリ外に移動
    Show Imageを動作させるためのおまじないとして、ビヘイビア(プロジェクト新規作成後はbehavior_1/behavior.xarというファイルで保存されている)の位置を変更します。以下のように、behavior.xarをドラッグして、behavior_1ディレクトリの外、プロジェクトファイルの最上位のファイルとなるように移動します
    ![move-behavior.png](https://qiita-image-store.s3.amazonaws.com/0/28770/fe4af104-a0ee-3db5-6ed8-06610a7f1118.png)
    おまじないの理由については、Show Imageがどう実現されているかの探検を通じて説明します。

これで、タブレットに画像を表示するアプリケーションができました。

###動作確認

動作確認するには、Pepperに接続し、再生をおこなってください。

なお、バーチャルロボットの場合は、Show Imageボックスがエラーを示す赤色になり、ログビューに以下のようなエラーメッセージが表示されます。

```
[ERROR] behavior.box :_getTabletService:20 _Behavior__lastUploadedChoregrapheBehavior1798150472__root__ShowImage_13: Cannot find service 'ALTabletService' in index
[WARN ] behavior.box :onInput_onStart:58 _Behavior__lastUploadedChoregrapheBehavior1798150472__root__ShowImage_13: No ALTabletService, can't display the image.
```

###(補足)Show Imageボックスの実装をみてみる

behavior.xarに関するおまじないの理由については、Show Imageボックスをみていくのが近道です。
Show Imageボックスは **Pythonボックス** であるため、フローダイアグラムボックスやタイムラインボックスとは異なり、ボックスをダブルクリックするとスクリプトエディタが開きます。

![script-editor.png](https://qiita-image-store.s3.amazonaws.com/0/28770/304c4533-938a-1897-b490-542ac6a053b4.png)

すこしコードの中身を見てみます。このコードのうち、onStartによって起動されるのは以下のコードです。

```py
    def onInput_onStart(self):
        # We create TabletService here in order to avoid
        # problems with connections and disconnections of the tablet during the life of the application
        tabletService = self._getTabletService()
        if tabletService:
            try:
                url = self.getParameter("ImageUrl")
                if url == '':
                    self.logger.error("URL of the image is empty")
                if not url.startswith('http'):
                    url = self._getAbsoluteUrl(url)
                tabletService.showImage(url)
            except Exception as err:
                self.logger.error("Error during ShowImage : %s " % err)
                self.onStopped()
        else:
            self.logger.warning("No ALTabletService, can't display the image.")
            self.onStopped()
```

バーチャルロボットではこのself._getTabletService()内で取得しようとするALTabletServiceが見つからなくてエラーとなってしまうわけです。

さて、おまじないに関係するのは、この、パスから絶対URLに変換する箇所 `self._getAbsoluteUrl(url)` です。
さらに見てみると、

```py
    def _getAppName(self):
        import os
        if self.frameManager:
            behaviorPath = os.path.normpath(self.frameManager.getBehaviorPath(self.behaviorId))
            appsFolderFragment = os.path.join("PackageManager", "apps")
            if not (appsFolderFragment in behaviorPath):
                self.logger.error("appsFolderFragment is not in behaviorPath")
            fragment = behaviorPath.split(appsFolderFragment, 1)[1]
            return fragment.lstrip("\\/")
        else:
            self.logger.warning("No ALFrameManager")

    def _getAbsoluteUrl(self, partial_url):
        import os
        subPath = os.path.join(self._getAppName(), os.path.normpath(partial_url).lstrip("\\/"))
        # We create TabletService here in order to avoid
        # problems with connections and disconnections of the tablet during the life of the application
        return "http://%s/apps/%s" %(self._getTabletService().robotIp(), subPath.replace(os.path.sep, "/"))
```

タブレットに指示するURLを生成する過程で、`self.frameManager.getBehaviorPath(self.behaviorId)` によって得られた、Show Imageボックスが所属するビヘイビアのパスを利用しています。ビヘイビアがプロジェクトファイルの最上位にないと、ここで想定しているURLが取得できず、画像の表示に失敗してしまいます。

なお、この問題を回避するには、ビヘイビアの位置を変える方法のほか、Show ImageボックスのPythonコードに以下のようなコードを挿入する方法もあります。

```py
    def _getAppName(self):
        import os
        if self.frameManager:
            behaviorPath = os.path.normpath(self.frameManager.getBehaviorPath(self.behaviorId))
            appsFolderFragment = os.path.join("PackageManager", "apps")
            if not (appsFolderFragment in behaviorPath):
                self.logger.error("appsFolderFragment is not in behaviorPath")
            fragment = behaviorPath.split(appsFolderFragment, 1)[1]
            # 以下の1行を追加
            fragment = fragment.split('/')[1]
            return fragment.lstrip("\\/")
        else:
            self.logger.warning("No ALFrameManager")
```

このようにしてPythonボックスの内容を確認してボックスに対する理解を深めたり、ログ出力コードを追加するなどの修正も可能です。

##映像の出力

画像ファイルと同じ要領で、映像ファイルの再生をすることもできます。mp4とmovに対応しています。

###つくってみる

1. 利用するボックスの準備 ... tabletタブのボックスを使います
    * tablet > Play Video
2. ボックスをつなぐ
    ![play-video-flow.png](https://qiita-image-store.s3.amazonaws.com/0/28770/87a12f84-440e-3f76-0bed-f96de6f851d9.png)
    * Play Videoボックスはビデオ再生終了時にonStoppedシグナルが送られますので、それにともなってビヘイビアも終了するようにします。

3. htmlディレクトリを作成し、映像ファイルをインポートする
    ![import-movie.png](https://qiita-image-store.s3.amazonaws.com/0/28770/0f97044e-855b-8b43-1161-06da8f174865.png)
    * 画像ファイルと同じ手順です

4. Play Videoボックスのパラメータに、映像ファイルへのパスを設定します
    ![play-video-props.png](https://qiita-image-store.s3.amazonaws.com/0/28770/5a1b4a69-f2b3-a606-3dd0-e169c03f3751.png)

5. **[おまじない]** ビヘイビアをディレクトリ外に移動
    画像ファイルの場合と同様、おまじないも実施しておきます

これで、映像を再生し、再生が終わったらビヘイビアも終了するようなアプリケーションを実現することができます。

###動作確認

Pepperに接続し、再生してみてください。タブレットで映像が再生されるはずです。

Play VideoボックスにはonPauseVideo(一時停止), onResumeVideo(再開)といった入力もあるので、たとえば自分の前に人がいると判断されるときだけ映像を再生し続け、人がいなくなったら映像を止める、といったことをおこなっても面白いかもしれません。

#ボックスの使い方：タッチイベントを取得する

タブレットに対するタッチイベントで処理をおこなうことも可能です。
ここでは、タブレットがタッチされたら「はい」としゃべらせてみます。

###つくってみる

1. 利用するボックスの準備
    * tabletタブ
        * tablet > Touch Detection ... タッチの検出
    * standard
        * Audio > Voice > Set Language ... 言語設定を変更する
        * Audio > Voice > Say ... こんにちはと言う 
2. ボックスをつなぐ
    ![tablet-flow.png](https://qiita-image-store.s3.amazonaws.com/0/28770/19272c39-62c0-8efe-4cb9-9bf9719b9754.png)

3. パラメータを設定する
    ![touch-params.png](https://qiita-image-store.s3.amazonaws.com/0/28770/701e915a-1a9b-456a-df44-5272cd123ce9.png)
    * Touch Detectionボックスのパラメータは、変数Actionを [On touch down]にしておきます
    * Set LanguageボックスはJapaneseを設定します

4. Sayボックスをカスタマイズする
    ![touch-say.png](https://qiita-image-store.s3.amazonaws.com/0/28770/36b3ad36-5f4b-93fa-88ae-334c974e5c58.png)
    * Sayボックスをダブルクリックし、中のLocalized TextをJapaneseで、はい と設定しておきます

これで、タッチされたときにSayボックスを実行することができるようになります。

###動作確認

Pepperに接続、再生し、タブレットにタッチしてみてください。「はい」と言えば成功です。

onTouched出力には、どの座標がタッチされたかという情報が渡されます。この情報により、Pepperの挙動を変えるなどができると面白いかもしれません。

タブレットはPepperから人間への情報提示をおこなう上で非常に強力な要素となります。ここで挙げた画像、動画以外にも、HTMLファイルの表示などさまざまなことをおこなうことが可能です。