飲み会の誘いを自分の代わりにしてくれるPepperを作ってみた

# 自己紹介
PepperAdvancedPartnerである
エコー電子工業(株)の井本です！
今まで、チームのみんなと一緒にPepperでいろんなアプリを開発してきたので、
その知識を生かして、日常の問題を解決しちゃいます！

# なぜ作るのか
最近こういう悩みを聞くことが多くなりました！
「今日、飲みに行きたい気分なんだけど、自分で誘うのめんどくさいなぁ、誰か私を誘ってほしいなぁ」

ということで、Pepperが参加者を勝手に集めて飲み会を開いてくれる非常に画期的なアプリが必要となったわけです！(*^^)v

# 必要な機能
今回必要な機能を簡単に分けると次の4つですね

*  飲みのお誘いのための**発話**
*  お店の情報を **画像表示**
* **SLAM**で席まで移動

## 飲みのお誘いのための発話
Pepperで発話するには,「say」と「AnimatedSay」の二つがあります。
簡単に説明すると次のようになりますね

| Say|AnimatedSay|
|:----|:----|
|発話のみ|モーション付きの発話|

今回は「モーションをつけるのが、めんどく...」ということで、
勝手にモーションがつく、AnimatedSayを使いたいと思います。

![AnimatedSay.PNG](https://qiita-image-store.s3.amazonaws.com/0/192308/1fa97acf-24b9-6d6b-2b89-33fddd7b50b9.png)

左下にある鍵マークをクリックして、ボックスの編集画面を開き、Textの中に「みんなと飲みに行きませんか？」という飲み会のお誘いワードを入れちゃいましょう！
Voiced shaping：声の高さは、少しテンション高めの「140」
Speed：文字を読むスピードは、「110」くらいに設定してみました!

![AnimatedSay_内部変更後.PNG](https://qiita-image-store.s3.amazonaws.com/0/192308/af378a85-09fe-e11d-29d3-1aa279804ced.png)


注)もし、下の画面が出て、再生できないって時には、Autonomousが効いていますので、下の画像のようにハートマークをクリックして、寝かせた後に、☆のようなマークをクリックして、起こしてあげてください！(ハートが白抜きになると思います。)

![autonomous.PNG](https://qiita-image-store.s3.amazonaws.com/0/192308/1f2c6f2c-687e-6ee9-8c7d-8ac27e7dc94c.png)
![autonomous2.PNG](https://qiita-image-store.s3.amazonaws.com/0/192308/e950f5be-bf84-bfa6-f0ae-8e3a8270e506.png)


## お店の情報を画像表示
次はお店の画像を表示させたいと思います。
今回は、OKをもらっていないお店の写真を記事にしちゃうのもどうかと思ったので、お酒の写真にしちゃいたいと思います！(*´ω｀*)

画像を表示する多面いShow Imageボックスを使いましょう！
![showIMage.PNG](https://qiita-image-store.s3.amazonaws.com/0/192308/3bc07004-c459-df17-2b8f-6c3b189dc8bc.png)

画像を表示させるにはアプリの中にhtmlフォルダを作成して、画像を入れる必要があるので、
プロジェクトファイルのプラスボタンを押して、新規フォルダを選択！
「html」と入力し、「create」ボタンを押してください(^^)/
![プロジェクトファイル.PNG](https://qiita-image-store.s3.amazonaws.com/0/192308/15f3f3e3-7ad7-0bef-fdca-3bb4013304de.png)
![プロジェクトファイル2.PNG](https://qiita-image-store.s3.amazonaws.com/0/192308/0bf55ac4-01e0-f7fe-b5ff-d51662c6c77d.png)
![プロジェクトファイル3.PNG](https://qiita-image-store.s3.amazonaws.com/0/192308/34f809b8-d766-e8dc-a0bb-377baef52464.png)

用意した画像をhtmlフォルダの中に入れましょう！
プロジェクトファイルのプラスボタンを押して、ファイルをインポートを選択！
画像を選んでください！
![プロジェクトファイル4.PNG](https://qiita-image-store.s3.amazonaws.com/0/192308/69443611-d79f-e7c5-94f2-6fda1e11f35a.png)


飲み会の画像をhtmlフォルダ内に入れてください！
![プロジェクトファイル5.PNG](https://qiita-image-store.s3.amazonaws.com/0/192308/dcbf3d11-6d09-9e67-7119-c1e397a40163.png)

それでは、画像を指定して、表示します！！
ShowImageボックスの鍵マークをクリックし、画像名を入れてください( ﾟДﾟ)
「./画像名」にしてくださいね。
![showImage2.PNG](https://qiita-image-store.s3.amazonaws.com/0/192308/7a4d3a05-332e-3d1e-db26-0f4a9f60055c.png)



こんな感じになりました！！
すでにかわいく飲みのお誘いはできるようになりましたね！！(^^)/
https://youtu.be/KaFdt8Ai7GI

## SLAM
それでは、SLAMの機能を使って行きましょう！(*´ω｀*)
PepperのSLAM機能は大きく分けて3つの工程に分かれます。
それが **マップの作成** , **目的地の設定** , **移動** です(*'▽')

### マップの作成
#### 探索
マップを作成するには、NaoqiのAPIのALNavigation.explore を実行します。

残念ながら、Navigationのボックスはないので、作成しましょう！
ボックスライブラリでPythonを選択してください
![ボックスライブラリ.PNG](https://qiita-image-store.s3.amazonaws.com/0/192308/bdab9a36-65ef-eb4a-98e0-c750c3fe4ce7.png)
![PythonScript.PNG](https://qiita-image-store.s3.amazonaws.com/0/192308/65e96baa-bb1b-0fe9-a53a-23eb68902955.png)

このPythonScriptボックスは何にも書いていないので、ここに、コードを書いていきます！
まずは、onload内でSLAMに必要なNavigationAPIを呼び出すコードを書き込みましょう！

```python

def onLoad(self):
        self.navigation = ALProxy("ALNavigation")
```

次に、onInput_onStart内に探索するためのAPI **ALNavigation.explore** を書き込みます

```python

def onInput_onStart(self):
        self.navigation.explore(3)
        self.onStopped()
```

それでは、実行させましょう！
動画の用に1回転して、動き始めますので、Pepperから離れて実行してください。
注)PepperはSLAMを行う際に赤外線レーザ・ソナーセンサーなどを用いて周辺を探索します。この赤外線の届く範囲が3mなので、3m以上人がいない場所で実行してください。
https://youtu.be/fLhPXeoJjMI

#### マップデータのロード・現在位置の取得

ALNavigation.exploreAPIは終了した際に返却値として、マップデータをPepperのどこに保存したのかを教えてくれます。
注)マップデータは、Pepperを再起動してもデータは消えません。

そこで、マップデータの取得・Pepperがマップ上のどこにいるのか取得をしたいと思います。
先ほどのボックスを次のように書き換えてください。


```python

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

    def onLoad(self):
        self.navigation = ALProxy("ALNavigation")


    def onUnload(self):
        self.navigation.stopLocalization()
        self.navigation = None

    def onInput_onStart(self):
        self.navigation.explore(3)
        mapDataPath = self.navigation.saveExploration()
        self.navigation.loadExploration(mapDataPath)
        self.navigation.startLocalization()
        pepperPosition = self.navigation.getRobotPositionInMap()[0]
        self.logger.fatal(pepperPosition)
        self.onStopped() #activate the output of the box

    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

```

ここで使用するAPIの簡単な説明をしたいと思います。

| explore | saveExploration | loadExploration |startLocalization|getRobotPositionInMap|
|:--|:--|:--|:--|:--|
|探索を行う。Pepperのメモリ内にマップデータを作成していく|メモリ内にためたマップをテキストファイルとして出力する|マップファイルから、データを取得する(/data/home/nao/.local/share/Explorer)|Pepperの周りの状況を把握する|Pepperがマップ上の現在どの位置にいるか把握する|
注)簡単に書いているので、詳しくは、NaoqiAPIリファレンスをご覧ください。

Pepperで実行すると次のようなログが出力されます。
この[0.0, 0.0, 0.0]というのがPepperのマップ上での現在位置です。

```
[FATAL] behavior.box :onInput_onStart:17 _Behavior__lastUploadedChoregrapheBehaviorbehavior_11728551872:/Python Script_5: [0.0, 0.0, 0.0] 
```

### 目的地の設定
PepperのSLAM機能を使って現在位置の取得までできたので、次は目的地を設定していきたいと思います。

**マップの作成** でも少し触れましたが、getRobotPositionInMapを使って、目的地の情報の保存を行いたいと思います。

1. マップデータの確認
    
マップデータを毎回作成するのは、非常に手間なので、マップデータパスをPepperのメモリに保存して、メモリが存在しなければ、探索することにしましょう。

exploreボックス

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

    def onLoad(self):
        self.navigation = ALProxy("ALNavigation")
        self.memory = ALProxy("ALMemory")

    def onUnload(self):
        #self.navigation.stopLocalization()
        self.navigation = None

    def onInput_onStart(self):
        try:
            self.navigation.stopLocalization()
        except:
            self.logger.info("finish localize")
        try:
            mapDataPath = self.memory.getData("pepper/slam/mapDataPath")
        except Exception as e:
            self.navigation.explore(3)
            mapDataPath = self.navigation.saveExploration()
            self.memory.insertData("pepper/slam/mapDataPath",mapDataPath)

        self.navigation.loadExploration(mapDataPath)
        self.navigation.startLocalization()
        pepperPosition = self.navigation.getRobotPositionInMap()[0]
        self.logger.fatal(pepperPosition)
        self.onStopped() #activate the output of the box

    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
```

2. Pepperの頭をタッチしたら、現在位置を取得し、メモリに保存する

Pepperの頭をタッチしたら、Pepperの現在位置を取得し、メモリにリスト形式で格納していくことにします。
Tactile Headボックスを使って、頭のタッチを検出しましょう！
Pepperには頭に3つのタッチセンサーがついているので、何度も保存されないようにonlyOnceボックスも使いましょう！

![heatTouch.PNG](https://qiita-image-store.s3.amazonaws.com/0/192308/5b131180-829b-ab21-1d72-7dfcebccabd2.png)


他にも保存処理が終わったら、Pepperが「保存しました」と発話するようにして、Pepperの左右のbumperのどちらかにタッチしたら、アプリが終了することにしましょう！

![データ保存.PNG](https://qiita-image-store.s3.amazonaws.com/0/192308/1ebc0d17-1ee3-65e1-f517-f7870e464c36.png)


次は、このgetPostionボックスを作っていきたいと思います。
このボックスで行いたいことは
getRobotPositionでPepperの現在位置を取得して、メモリにリストとして保存することですね。

getPostionボックス

```python

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

    def onLoad(self):
        self.navigation = ALProxy("ALNavigation")
        self.memory = ALProxy("ALMemory")


    def onUnload(self):
        #self.navigation.stopLocalization()
        self.navigation = None

    def onInput_onStart(self):
        pepperPosition = self.navigation.getRobotPositionInMap()[0]
        self.logger.fatal(pepperPosition)
        positionList = list()
        try:
            positionList = self.memory.getData("pepper/slam/positionList")
        except:
            self.memory.insertData("pepper/slam/positionList",positionList)
            
        positionList.append(self.navigation.getRobotPositionInMap()[0])
        self.memory.insertData("pepper/slam/positionList",positionList)
        self.onStopped() #activate the output of the box


    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


```

### 移動

実際に移動するボックスを作ってみたいと思います。
先ほどまでは、保存処理を行っていましたが、次は、Pepperの手に触れると、保存した場所を周遊してみたいと思います。



navigateボックス

```python

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

    def onLoad(self):
        self.navigation = ALProxy("ALNavigation")
        self.memory = ALProxy("ALMemory")
        self.tts = ALProxy('ALTextToSpeech')

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

    def onInput_onStart(self):
        positionList = self.memory.getData("pepper/slam/positionList")
        count = 0
        for position in positionList:
            count+=1
            self.navigation.navigateTo(position[0],position[1],position[2]) 
            self.tts.say("目的地"+str(count)+"に到着しました。")
            
        self.onStopped() #activate the output of the box

    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

```

![データ保存2.PNG](https://qiita-image-store.s3.amazonaws.com/0/192308/4790822e-c78b-9e41-bd2b-a7cac5200f7b.png)

これを実行してみると
このようになりました！(^_-)-☆

https://youtu.be/qVgjbXgSUI4




## 全部くっつける！

ついに、同僚のところまでPepperが歩いて行って、飲みに誘うというミッションを行ってもらいます！( *´艸｀)

navigateボックスを少し、変更してのみの誘いのボックスとくっつけてしまいたいと思います！！！

navigateボックス

```python

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

    def onLoad(self):
        self.navigation = ALProxy("ALNavigation")
        self.memory = ALProxy("ALMemory")
        self.tts = ALProxy('ALTextToSpeech')
        self.count = 0

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

    def onInput_onStart(self):
        positionList = self.memory.getData("pepper/slam/positionList")
        self.count = 0
        position = positionList[0]
        self.navigation.navigateTo(position[0],position[1],position[2]) 
        if len(positionList) >= self.count:
            self.count+=1
            self.outSay()
        else:
            self.onStopped() 
        
    def onInput_nextStart(self):
        positionList = self.memory.getData("pepper/slam/positionList")
        
        if len(positionList) >= self.count+1:
            position = positionList[self.count]
            self.navigation.navigateTo(position[0],position[1],position[2]) 
            self.count+=1
            self.outSay()
        else:
            self.onStopped() 
        

    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


```

## ソースと実際に動いた動画
https://youtu.be/O3j3nnhKCOM

[gitソース](https://github.com/kenimo49/pepperSlam)

後輩の長尾君に協力して撮影しました！
残念ながら今回は断られてしまったので、またレベルアップして誘いに行きたいと思います。


# まとめ
このPepperアプリを使えば、誰が飲みに誘ったかもばれずに、気分次第で毎日、飲み会が開けちゃいますね！！
実際に使うときは、場所と人を紐づけたり、「行く、行かない」ボタンを表示させたり
こだわりどころたくさんあるので、やってみてくださいね。

注）今回は、位置データをメモリに保存しているので、再起動をかけた場合消去されてしまいます。実際に使いたい方は、htmlフォルダの中にjsonファイルやtextファイルを作成してください(^^♪