HTTP Getボックスの作成

#Pepperからのネットワーク利用

これまでのチュートリアルでは、センサーからの情報によりPepperの挙動を変えたり、音声や動き、タブレットなどを通じてPepperから利用者に対して情報を提示したりと、同じ空間にいる利用者とのコミュニケーションについて主に見てきました。
一方で、Pepperの持つネットワーク機能を用いることで、Pepperが利用者に何らかの情報を送ったり、ネットワークから得た情報に基づいてPepperが動きを変えたりすることも可能です。

たとえば、ここまでチュートリアルとしてみてきた標準ボックスライブラリには、 CommunicationフォルダにあるE-mail, Networkフォルダに、メールの受信、送信、ネットワーク接続の有無のチェックをおこなうボックスがあります。
また、[Pythonボックス](http://qiita.com/Atelier-Akihabara/items/f6a05ca14be786f9a94c)の仕組みを活用することで、Pythonスクリプトを用いてよりさまざまな機能を実現することができます。

ここでは、**入力されたURLからデータを取得し、そこで得られたテキストを出力する**ボックスの作成を例として、Pythonボックスの作成方法や、考え方について具体的に見ていきます。

#HTTP Getボックスの作成

今回は、Pythonの標準的なライブラリである [urllib2](http://docs.python.jp/2.7/library/urllib2.html)と[HTMLParser](http://docs.python.jp/2.7/library/htmlparser.html) を用いて、以下のようなボックスの作成を考えていきます。

![httpget-box-design.png](https://qiita-image-store.s3.amazonaws.com/0/54394/fbe24996-d033-b4a4-909a-34e1414debd2.png)

HTTP Getボックスの仕様としては、

1. 入力onStartは文字列型で、読み込みたいURLを受け取る
2. onStart内でHTTPのGET処理をおこなう
    1. 成功した場合は、得られた内容をともなってonStoppedを出力する
    2. 失敗した場合は、エラーメッセージをともなってonFailedを出力する

今回はサンプルとして、onStoppedの出力文字列はSay Textのようなボックスへの接続を想定します。特に、HTML `<body>` タグ以下のみを抽出し、すべてのタグは除去し、テキスト中の改行箇所に対して200ミリ秒待機するような制御命令を埋め込むものとします。

また、これらの動作は、以下のパラメータにより挙動を調整できるものとします。

|パラメータ名|機能|タイプ|値の範囲|デフォルト値|
|-----------|----|---|---|---|
| Timeout | urllib2.urlopenの際のタイムアウト値(秒) | 整数 | 1～3600 | 30 |
| Start line | 得られたテキストのうち、改行文字で区切られた各行のうちどの行からを出力するか (開始行=0) | 整数| 0～10000 | 0 |
| Max lines | 出力する最大行数 | 整数| 1～10000 |  1 |


なお、実際にボックスとして作成する際は、HTTP Getボックスと、得られたHTML文字列を適切に加工するボックスを分けたほうが、より汎用性が高いボックスになるかもしれません。また、パラメータの設定もサンプルとして決めたものですので、利用場面など考慮の上アレンジを加えていってみてください。

> ####![info.png](https://qiita-image-store.s3.amazonaws.com/0/54394/ed7863bd-4c2e-7710-58db-0e38a4f26959.png) (注意)HTTP Getボックスで処理できるコンテンツについて
> この例では、デモとしてのボリュームを考え、例としてQiitaのHTMLを前提とし、標準ライブラリを使った簡易的なHTMLの解析を実装しています。
> 実際に外部のサービスを利用するためには、このような簡易的なHTMLのスクレイピングでは難しい場合も多いため、サービスが提供するAPIの利用を考えるなどしていただく必要があります。この場合、これから説明するPythonスクリプト部分を独自に実装していただく必要があります。
> Pythonを使ってAPIにアクセスする方法などはそれぞれのサービスの説明などを参考にしてください。


###Pythonボックスの作成

まず、Pythonスクリプトを記述するための空のボックスを作成します。新規プロジェクトを作成し、以下の手順で操作していってください。

1. フローダイアグラム上で右クリックをおこない、**[ボックスの新規作成]の[Python...]を選択**します
    ![new-python-box.png](https://qiita-image-store.s3.amazonaws.com/0/54394/8d6a3d0a-2bdc-e175-d693-57469d455dfa.png)

2. 名前に **HTTP Get と入力**します
    ![add-new-box.png](https://qiita-image-store.s3.amazonaws.com/0/54394/911096eb-61db-89e9-eb6d-61ad6a47def7.png)

3. onStart入力の型を文字列に変更します。入力 onStart の**設定ボタンをクリック[A]**し、**Typeドロップダウンリストをクリック[B]**します
    ![edit-onstart-type-1.png](https://qiita-image-store.s3.amazonaws.com/0/54394/6fbae449-d885-36e9-982b-5566a152d592.png)

    すると、タイプの一覧があらわれますので、**[文字列] を選択**し、[OK]ボタンをクリックします
    ![edit-onstart-type-2.png](https://qiita-image-store.s3.amazonaws.com/0/54394/cbe61b7c-f3bc-dd02-191a-20212f80d78b.png)

4. デフォルトで用意されるonStop入力を削除します。入力の**[onStop]を選択[A]**し、**削除[-]ボタンをクリック**します
    ![remove-onstop-1.png](https://qiita-image-store.s3.amazonaws.com/0/54394/e06c212c-73bb-3dae-4228-255ecf11a0e7.png)

    すると、削除の確認ダイアログが開くので、[はい]ボタンを押します
    ![remove-onstop-2.png](https://qiita-image-store.s3.amazonaws.com/0/54394/37ffb923-ff5f-5f0d-247c-241fb6ed6374.png)

5. onStopped出力の型を文字列に変更します。出力 onStopped の**設定ボタンをクリック[A]**し、**Typeドロップダウンリストから[文字列] を選択[B]**し、[OK]ボタンをクリックします
    ![edit-onstopped-type-1.png](https://qiita-image-store.s3.amazonaws.com/0/54394/aef01ab3-8b2f-b96a-77af-2706908a1f58.png)

6. 次に、出力にonFailedを追加します。出力の **追加[+]ボタンをクリック**します
    ![add-onfailed-1.png](https://qiita-image-store.s3.amazonaws.com/0/54394/ef2e14cb-90bd-aa4a-7e9c-818385fbf0f6.png)

    入力onStart, 出力onStoppedの際と同様に設定ダイアログがあらわれますので、Nameに **onFailed と入力 [A]**し、Typeを**[文字列]に変更 [B]**します
    ![add-onfailed-2.png](https://qiita-image-store.s3.amazonaws.com/0/54394/7159f409-69d4-e830-f3e6-33868a66eb86.png)

7. パラメータを追加します。変数の **追加[+]ボタンをクリック [A]**し、Nameに **Timeout と入力 [B]**、Typeを**[整数]に変更 [C]**すると、デフォルト値、値の範囲を入力するボックスがあらわれるので **デフォルト値, 最小値, 最大値を入力 [D]**し、[OK]ボタンをクリックします
    ![add-timeout-param-1.png](https://qiita-image-store.s3.amazonaws.com/0/54394/67ba0f76-9796-05bf-4b66-6bcdcfa743aa.png)

    [OK]ボタンを押すと、変数リストに Timeoutが追加されます
    ![add-timeout-param-2.png](https://qiita-image-store.s3.amazonaws.com/0/54394/f1978724-c520-a7c1-7211-e6a9e7cd3471.png)

8. 7.と同じ要領で、Start line, Max linesを追加します
    ![add-start-line.png](https://qiita-image-store.s3.amazonaws.com/0/54394/638932ba-b9ae-2ad0-e506-430f5d46d788.png)

    ![add-max-lines.png](https://qiita-image-store.s3.amazonaws.com/0/54394/09341a41-8e52-5ffc-845c-e469ffa80ca2.png)

    変数の一覧に、Timeout, Start line, Max linesが追加されていることを確認します
    ![added-params.png](https://qiita-image-store.s3.amazonaws.com/0/54394/b514ed92-3123-b169-bb1d-17535ca2e765.png)

7. これでボックスの入出力、パラメータの準備は完了です。[OK]ボタンを押すと、フローダイアグラム中に **HTTP Getボックス** が作成されます
    ![new-python-box.png](https://qiita-image-store.s3.amazonaws.com/0/54394/0837e617-5280-98f7-b619-481ccedee414.png)


これで空のボックスができました。次に、このボックス内にスクリプトを記述します。

###Pythonスクリプトの記述

HTTP Getボックスをダブルクリックしてスクリプトエディタを開き、以下のスクリプトを貼りつけてください。

```py
class MyClass(GeneratedClass):
    def __init__(self):
        GeneratedClass.__init__(self)

    def onLoad(self):
        #put initialization code here
        pass

    def onUnload(self):
        #put clean-up code here
        pass

    def onInput_onStart(self, url):
        from HTMLParser import HTMLParser
        import urllib2, contextlib

        class ContentParser(HTMLParser):
            def __init__(self):
                HTMLParser.__init__(self)
                self.inscript = False
                self.inbody = False
                self.text = ""

            def handle_starttag(self, tag, attrs):
                if tag == "body":
                    self.inbody = True
                if tag == "script":
                    self.inscript = True

            def handle_endtag(self, tag):
                if tag == "body":
                    self.inbody = False
                if tag == "script":
                    self.inscript = False

            def handle_data(self, data):
                if self.inbody and not self.inscript:
                    self.text = self.text + data

        try:
            timeout = int(self.getParameter("Timeout"))
            self.logger.info("Loading... %s" % url)
            with contextlib.closing(urllib2.urlopen(url, None, timeout)) as response:
                parser = ContentParser()
                parser.feed(response.read())
                parser.close()

                lines = [line for line in parser.text.splitlines() if len(line.strip()) > 0]
                startline = int(self.getParameter("Start line"))
                maxlines = int(self.getParameter("Max lines"))
                text = "\\pau=200\\".join(lines[startline:startline + maxlines])
                self.logger.info("Loaded: %s" % text)
                self.onStopped(text)
        except urllib2.URLError, message:
            self.logger.warn("Failed to open: %s, message=%s" % (url, message))
            self.onFailed(message)
```

コードのポイントは以下の通りです。

1. onStart入力に対する処理を、 `onInput_onStart(self, url)` メソッドによって定義しています (参考: [Pythonボックスの考え方#入力](http://qiita.com/Atelier-Akihabara/items/f6a05ca14be786f9a94c#%E5%85%A5%E5%8A%9B))

    ```py
        def onInput_onStart(self, url):
            ...
            self.logger.info("Loading... %s" % url)
            ...
                with contextlib.closing(urllib2.urlopen(url, None, timeout)) as response:
    ```

    引数urlには、onStartに入力された値(今回は文字列)が設定されます。

2. Timeoutなどのパラメータは、 `self.getParameter` メソッドを呼び出すことによって取得しています (参考: [Pythonボックスの考え方#パラメータ](http://qiita.com/Atelier-Akihabara/items/f6a05ca14be786f9a94c#%E3%83%91%E3%83%A9%E3%83%A1%E3%83%BC%E3%82%BF))

    ```py
                timeout = int(self.getParameter("Timeout"))
    ```

3. 得られた文字列は、 <出力名> メソッドを呼び出すことで渡しています (参考: [Pythonボックスの考え方#出力](http://qiita.com/Atelier-Akihabara/items/f6a05ca14be786f9a94c#%E5%87%BA%E5%8A%9B))

    ```py
                    text = "\\pau=200\\".join(lines[startline:startline + maxlines])
                    self.logger.info("Loaded: %s" % text)
                    self.onStopped(text)
    ```

    > ####![info.png](https://qiita-image-store.s3.amazonaws.com/0/54394/ed7863bd-4c2e-7710-58db-0e38a4f26959.png) (参考)say文字列中の制御文字列 \pau=200\ について
    > この例では、Say Textボックス(ALTextToSpeech APIのsay()を使ったボックス)に文字列を渡す前提で、話す際の一時停止指定を埋め込んでいます。
    > これが \pau=200\ という文字列で、200ミリ秒の一時停止(pause)を指示しています。
    > 他にも、さまざまな制御をおこなうことができます。詳しくは、ドキュメント中の ALTextToSpeech を検索し、 Overview > Acapela Mobility Text TAGS Documentation を参照してください。

#動作確認

動作確認として、**[Pepperチュートリアル (1)：SDKインストールとアプリケーションの作成/実行](http://qiita.com/Atelier-Akihabara/items/c5f57358a7b333eef397)の出だしを、ジェスチャー付きで読み上げる** ということをおこなってみます。

1. 作成したHTTP Getボックスに加え、standardボックスライブラリの以下のボックスを配置します。
    * Data Edit > Text Edit
    * Audio > Voice > Animated Say
    
    ![sample-flow-base.png](https://qiita-image-store.s3.amazonaws.com/0/54394/60aab8b8-b5e0-5a70-d874-725ddd607262.png)

    > ####![info.png](https://qiita-image-store.s3.amazonaws.com/0/54394/ed7863bd-4c2e-7710-58db-0e38a4f26959.png) (参考)Animated Sayボックス について
    > Animated Sayボックスは、ここまで見てきたSayボックスと異なり、しゃべるのと同時に動きをつけることができます。
    > この身振り手振りは自動的に付加されますが、文字列中のアノテーションによって明示することも可能です。
    > 詳しくは、ドキュメント中の ALAnimatedSpeech を検索し、Overview などを参照してください。

2. Animated SayボックスはSayボックス同様、Localized Textボックス＋Say Textボックスといった形ですので、この中から文字列を受け取るAnimated Say Textボックスを取り出します
    * Animated SayボックスをダブルクリックしAnimated Sayボックスの中に入ります。そこにあるAnimated Say Textボックス(名前の表示が[Animated Say]となっていますが、マウスオーバーすると[Animated Say Text]であることが確認できます)を右クリックし、**[切り取り]を選択**します
        
        ![cut-animated-say-text.png](https://qiita-image-store.s3.amazonaws.com/0/54394/0e3b1561-b35b-4c53-b245-9af1628647b4.png)
    * rootのフローダイアグラムに戻り、**貼り付けを選択**します

        ![paste-animated-say-text.png](https://qiita-image-store.s3.amazonaws.com/0/54394/b0125850-6e11-d66b-7d7a-32c6e0e9556b.png)
        Animated Say Textボックスがあらわれることを確認します。

        ![animated-say-text.png](https://qiita-image-store.s3.amazonaws.com/0/54394/a1c70ec6-1d5c-2395-92d5-006aa9adb6aa.png)
    * もとのAnimated Sayボックスは不要なので削除します

        ![remove-animated-say.png](https://qiita-image-store.s3.amazonaws.com/0/54394/eaf4e5d8-25ac-2958-046e-5a0710a940f0.png)
    * Animated Say Textボックスの位置を調整します

        ![sample-flow.png](https://qiita-image-store.s3.amazonaws.com/0/54394/c08134e5-80d9-2d7d-a1b3-5acb20ea0a93.png)


3. ボックスを以下のようにつなぎます
    ![connect-sample-flow.png](https://qiita-image-store.s3.amazonaws.com/0/54394/dface37a-cd42-4b91-2a62-31d23da56a26.png)

4. Text Editに、 **http://qiita.com/Atelier-Akihabara/items/c5f57358a7b333eef397 を貼りつけ**ます
    ![data-sample-flow.png](https://qiita-image-store.s3.amazonaws.com/0/54394/9926ac42-d5ff-e4fd-8163-4da7e53fe212.png)

5. パラメータを以下のように設定します
    ![param-sample-flow.png](https://qiita-image-store.s3.amazonaws.com/0/54394/b2b7e58f-62d2-08fc-2729-ef9756b5c7a7.png)


バーチャルロボットで動作確認をおこなうと、ダイアログパネルにテキストが表示されます。

![dialog-panel.png](https://qiita-image-store.s3.amazonaws.com/0/54394/e2240193-7359-f3e1-6059-5de77d22a951.png)

また、Pepper実機で再生をおこなうと、身振りを交えながらPepperがチュートリアルの内容をしゃべっていきます。(リンクをクリックして再生できます。)

[![HTTP Getボックス動作例](http://img.youtube.com/vi/RC4WHSGVGR0/0.jpg)](http://www.youtube.com/watch?v=RC4WHSGVGR0)
(再生時間: 1分)

今回は改行に対して機械的に待ち時間を入れたため、区切りなどが自然ではない部分がありますが、テキストタグを適切に設定していくことで、自由に制御することができます。

このようにして、ネットワークからデータを取得してPepperの挙動に反映させたりといったことが、Pythonボックスを利用することで実現できます。