Pepper用Tweetボックスをつくる

Pepperでモーションを作ったり会話を作ったりしているとそれはそれで非常に面白い感じがするのですが、やっぱりソーシャルなネットワークと接続したくなってしまいます。

[Pythonボックス](http://qiita.com/Atelier-Akihabara/items/f6a05ca14be786f9a94c)でPythonを書いちゃえばいろいろできるわけですが、公式ドキュメントをみても、外部のライブラリをimportする方法が書いてない・・・
と思ったら[HOW TO IMPORT PYTHON FILES IN YOUR PEPPER APPS?](http://www.about-robots.com/how-to-import-python-files-in-your-pepper-apps.html)という記事があったので試してみることにします。


#Twitter Tweetボックスの作成

まずはじめに何が必要そうかのアタリをつけてみます。
[Python で Twitter API にアクセス](http://qiita.com/yubais/items/dd143fe608ccad8e9f85) など読ませていただくに、Twitterへのアクセスには、以下の要素が必要になりそう。

* Twitter APIへのアクセスに必要なキー類
    * Consumer Key
    * Consumer Secret
    * Access Token
    * Access Token Secret
* Twitter APIに投稿したい内容
    * ステータス(メッセージ)文字列

また、スクリプトからインポートされるライブラリとして、以下が必要になるかなと。

* [requests-oauthlib](https://github.com/requests/requests-oauthlib)

これらを踏まえて、以下のようなTwitter Tweetボックスを実現することを考えていくことにします。


![tweet-design.png](https://qiita-image-store.s3.amazonaws.com/0/54394/81e77e77-b4d9-ca9b-9697-05d459206ac6.png)


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

1. 入力onStartは文字列型で、ツイートしたい内容messageを受け取る
2. onStartに入力がされると、ツイート処理がおこなわれる
    1. 成功した場合はonStoppedが出力される。値はなし(「バン」タイプ)
    2. 失敗した場合はonFailedが出力される。ステータスコード(数値)が渡される
3. Twitter APIへのアクセスに必要な Consumer Key, Consumer Secret, Access Token, Access Token Secretはパラメータによって与えられる
4. Twitter APIへのアクセスはPythonによって記述し、importするOAuthのライブラリはプロジェクトのファイルとして保存する

という感じ。手順としては、

1. Choregraphe上でPythonボックスを作る
2. Pythonスクリプトからimportするライブラリを準備する
3. Pythonスクリプトを記述する

の順番で試していくことにします。

##つくってみる

###ボックスの作成

まずは、Choregraphe上でボックスの形を作成していきます。

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

2. 名前に **Tweet と入力**
    ![add-new-box.png](https://qiita-image-store.s3.amazonaws.com/0/54394/d64cefed-90fd-706c-8aa2-2ad1e28b2062.png)

3. onStart入力の型を文字列に変更する。入力 onStart の**設定ボタンをクリック[A]**し、**Typeドロップダウンリストをクリック[B]**
    ![edit-onstart-type-1.png](https://qiita-image-store.s3.amazonaws.com/0/54394/c668c586-e6ff-d2c7-bf63-1be3ec8d8514.png)

    すると、タイプの一覧があらわれるので、**[文字列] を選択**し、[OK]ボタンをクリック
    ![edit-onstart-type-2.png](https://qiita-image-store.s3.amazonaws.com/0/54394/550bb761-6a34-f18e-7197-03dc467b861c.png)


4. 次に、出力にonFailedを追加。出力の **追加[+]ボタンをクリック**
    ![add-onfailed-1.png](https://qiita-image-store.s3.amazonaws.com/0/54394/14098223-1bbe-00b8-cc80-0e38f78c5c50.png)

    入力と同様に設定ダイアログがあらわれるので、Nameに **onFailed と入力 [A]**し、Typeを**[数]に変更 [B]**
    ![add-onfailed-2.png](https://qiita-image-store.s3.amazonaws.com/0/54394/c13ebf45-aff1-7c97-ace5-3bfc85ac8ec9.png)

5. パラメータを追加する。変数の **追加[+]ボタンをクリック [A]**し、Nameに **Consumer Key と入力 [B]**、Typeを**[文字列]に変更 [C]**し、[OK]ボタンをクリック
    ![add-param-1.png](https://qiita-image-store.s3.amazonaws.com/0/54394/3146c37c-ca61-5008-c929-a3f6b035983d.png)

    [OK]ボタンを押すと、変数リストに Consumer Keyが追加されたのがわかる
    ![add-param-2.png](https://qiita-image-store.s3.amazonaws.com/0/54394/05312853-569c-3f51-58bb-a7fc7fbbbf6d.png)

6. 5.と同じ要領で、Consumer Secret, Access Token, Access Token Secretを追加
    ![add-param-3.png](https://qiita-image-store.s3.amazonaws.com/0/54394/272e33c4-c07f-687e-e8cd-af8e7fc7dbce.png)

7. [OK]ボタンを押すと、フローダイアグラム中に **Tweetボックス** が作成される
    ![new-tweet-box.png](https://qiita-image-store.s3.amazonaws.com/0/54394/e885898c-a807-d77d-aa49-270452b36402.png)

これでボックスの外身の準備はOK。

###ライブラリの準備

TweetボックスではOAuthの認証のため、以下のライブラリをインポートすることにします。

* [requests-oauthlib](https://github.com/requests/requests-oauthlib)

このライブラリの [requirements.txt](https://github.com/requests/requests-oauthlib/blob/master/requirements.txt) を見ると、以下のライブラリも必要であることがわかったので、これらも含めることにします。

* [requests](https://github.com/kennethreitz/requests)
* [oauthlib](https://github.com/idan/oauthlib)

本来はパッケージ管理ツールとかちゃんと使うべきかなと思いつつ、まずはえいやで、以上3つのライブラリを使用してみる感じで、以下の手順でプロジェクトにライブラリのファイルを組み込んでみます。

1. [requests-oauthlib](https://github.com/requests/requests-oauthlib), [requests](https://github.com/kennethreitz/requests), [oauthlib](https://github.com/idan/oauthlib) の3つのリポジトリからファイルを取得
    ![git-download-zip.png](https://qiita-image-store.s3.amazonaws.com/0/54394/7e7fabf8-c543-bc51-4663-763692f0eb02.png)

    今回は **[Download ZIP]** を選択し、リポジトリ内のファイルをZIPでダウンロードする。

2. 適当なフォルダに `lib` フォルダを作成し、ダウンロードしたZIPファイル3つを展開したものを以下のような構成にまとめる
    * `lib`
        * `requests_oauthlib`
            * `__init__.py`
            * ...
        * `oauthlib`
            * `__init__.py`
            * ...
        * `requests`
            * `__init__.py`
            * ...


2. [プロジェクトの内容]パネルの**追加[+]ボタンをクリックし、[フォルダーのインポート...]を選択**
    ![import-folder.png](https://qiita-image-store.s3.amazonaws.com/0/54394/4f549ce6-acaa-7e2d-8c90-fbd0ab358453.png)

3. [インポートするフォルダーを選択]ダイアログが開くので、2.で作成した `lib` フォルダを選択し、**[フォルダーの選択]ボタンをクリック**
    ![import-folder-dialog.png](https://qiita-image-store.s3.amazonaws.com/0/54394/c5d3d6cc-d35a-1efc-5bf1-8ce34139f75f.png)

4. ファイルリストにlibディレクトリが追加されるので、ディレクトリツリーを展開し、ファイルがインポートされていることを確認
    ![imported-lib.png](https://qiita-image-store.s3.amazonaws.com/0/54394/329e5e91-95c4-eeba-cc27-bed4fcdcabf4.png)

これで、プロジェクトのファイルとしてPythonのライブラリをインポートすることができた（はず）。
次に、TweetボックスのPythonスクリプトとして、これらのライブラリを使用してツイートするようなコードを記述していきます。

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

Tweetボックスをダブルクリックしてスクリプトエディタを開き、以下のスクリプトを貼りつけます。

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

    def onLoad(self):
        self.framemanager = ALProxy("ALFrameManager")
        self.folderName = None

    def onUnload(self):
        import sys
        if self.folderName and self.folderName in sys.path:
            sys.path.remove(self.folderName)
        self.folderName = None

    def onInput_onStart(self, p):
        import sys, os
        self.folderName = os.path.join(
                    self.framemanager.getBehaviorPath(self.behaviorId), "../lib")
        if self.folderName not in sys.path:
            sys.path.append(self.folderName)
        for moduleName in os.listdir(self.folderName):
            # モジュールのreload
            if moduleName in sys.modules:
                self.logger.info("Loaded: %s, %s" % (moduleName, sys.modules[moduleName].__file__))
                reload(sys.modules[moduleName])
        from requests_oauthlib import OAuth1Session
        self.logger.info("ツイートしています... %s" % p)

        url = "https://api.twitter.com/1.1/statuses/update.json"

        # ツイート本文
        params = {"status": p}

        twitter = OAuth1Session(self.getParameter("Consumer Key"),
                        self.getParameter("Consumer Secret"),
                        self.getParameter("Access Token"),
                        self.getParameter("Access Token Secret"))
        req = twitter.post(url, params = params)

        if req.status_code == 200:
            self.logger.info("OK")
            self.onStopped()
        else:
            self.logger.warn("Failed: %d" % req.status_code)
            self.onFailed(req.status_code)

    def onInput_onStop(self):
        self.onUnload() #it is recommended to reuse the clean-up as the box is stopped
        self.onStopped() #activate the output of the box
```

コードのポイントとしては・・・

1. まず、onLoad時にALFrameManagerへの参照(プロジェクト内ファイルのパス解決に利用)取得と、変数の初期化を実施

     ```py:onLoad
     def onLoad(self):
         self.framemanager = ALProxy("ALFrameManager")
         self.folderName = None
     ```

2. onStartの処理開始時に `sys.path` によって [プロジェクトの内容]に追加したPythonライブラリをインポート可能に

     ```py:onInput_onStart
     def onInput_onStart(self, p):
         import sys, os
         self.folderName = os.path.join(
                     self.framemanager.getBehaviorPath(self.behaviorId), "../lib")
         if self.folderName not in sys.path:
             sys.path.append(self.folderName)
     ```

3. プロジェクト内のPythonライブラリをロードし、処理をおこなう

     ```py:onInput_onStart
        from requests_oauthlib import OAuth1Session
        self.logger.info("ツイートしています... %s" % p)
        ...
     ```

4. 処理完了後、Twitter API呼び出しの戻り値により出力を呼び出す

     ```py:onInput_onStart
        ...
        if req.status_code == 200:
            self.logger.info("OK")
            self.onStopped()
        else:
            self.logger.warn("Failed: %d" % req.status_code)
            self.onFailed(req.status_code)
     ```

5. アンロード時、 `sys.path` からプロジェクトのPythonライブラリに関するパスを削除

     ```py:onUnload
    def onUnload(self):
        import sys
        if self.folderName and self.folderName in sys.path:
            sys.path.remove(self.folderName)
        self.folderName = None
     ```

といった感じ。個人的には、 `sys.path` 書き換えは多少強引な感じもする・・・（アプリケーションごとにこれをやったらConflictしそうだし）
将来的にはpipとかがうまく統合されるとよいなあ、などと思いつつ。

##動作確認

作成したTweetボックスを試すため、以下のようなアプリケーションを作ってみる。

1. standardボックスライブラリの Data Edit > Text Edit を配置
    ![test-box-1.png](https://qiita-image-store.s3.amazonaws.com/0/54394/ab5b6fe4-58b7-f720-1655-fe962dc9ec25.png)

2. ボックスを以下のようにつなぐ
    ![test-box-2.png](https://qiita-image-store.s3.amazonaws.com/0/54394/8194cfb7-3131-59c1-41d3-3f2c070b39ea.png)

3. Tweetボックスのパラメータに、Twitterから取得した **Consumer Key, Consumer Secret, Access Token, Access Token Secret** を設定
    ![test-box-3.png](https://qiita-image-store.s3.amazonaws.com/0/54394/0ececbda-9006-017d-8cb5-a2418fb81fbe.png)

3. Text Editボックスにツイートしたい内容を入力
    ![test-box-4.png](https://qiita-image-store.s3.amazonaws.com/0/54394/714ca5a1-48cf-aa75-c625-d7d8e24e894a.png)


これで、任意のメッセージをツイートするフローを作成できた。Pepper実機もしくはバーチャルロボット(Macのみ)で動いた。
Windowsのバーチャルロボットでは実行不可。sslモジュールが有効でないっぽい・・・

こんな感じで、Pepperにツイートさせたりできる。カメラの画像を撮ってツイートとかできると、ハッカソン実況の仕事もPepperに任せられるな・・・などと思いつつ。今後も何かネタができたら書いていきます。