Pepper タブレットで HTML 表示して色々やる、あるいは Mashup Award 地区予選を突破した話


Mashup Award 2017 の福井予選にて、 生産管理部業務効率課 進捗課長 という Pepper アプリを作った。その時に得た知見をまとめる

主に担当した部分

タブレット部分……だが、タブレットのタッチイベントをきっかけとして Pepper が動き出すような仕組みになっているため、個々で作ったモジュールをここで結合するのが良いだろうということで、結構色々やった。主にこんな感じ:

  • スタート画面がタップされたら肩たたきモジュールを呼び出す
  • 肩たたきが完了したら進捗選択画面を表示する
  • 進捗を選択したら再び肩たたきモジュールを呼び出す
  • 全員の進捗を確認し終えたら動画作成モジュールを呼び出す
  • 動画作成が完了したら pepper を喋らせて動画再生モジュールを呼び出す
  • 動画再生が完了したら一番進捗の悪かった人へもったいぶって移動
    • ここはモジュールじゃなく JavaScript でやった
  • 支払い完了したらお辞儀させて Square API を叩くモジュールを定期的に呼び出す
  • Square に入金があったら「お支払いありがとうございました」

Choregraphe (コレグラフ) のクセ

2.5.5 を使ったが、別の人から接続を奪った後、最初の転送は結構待つ必要がある。
かつ、その間に「動かないな〜」などと言いながらボタンをあちこち押すとコレグラフが死ぬ。
とにかくおとなしく待とう。

他の人が作ったモジュールをインポート

左下ボックスライブラリの「ボックスライブラリを開く」(フォルダ開くみたいなアイコン)から。

なお、「新規ボックスライブラリ」アイコンを使って人から貰ったモジュールを選択すると、そのモジュールを容赦なく空モジュールで上書きする。
自分は最初それをやらかしてしまい、しかもその操作によって上書きされてることに気づかず、空モジュールをインポート → なんか読み込んでも空なんだけど(当たり前) というのでハマりまくった。

モジュール呼び出し

JavaScript で ALMemory のイベントを発生させ、 Choregraphe の GUI エディタでそのイベントをハンドリングしてモジュールを呼び出すように設定する。

こんな便利関数を作っといた:

function raiseEvent(key, value = 'push') {
  session.service("ALMemory").then(ALMemory => {
    ALMemory.raiseEvent('Shinchokudo/' + key, value)
  })
}

※JS は babel で変換してから突っ込んでるので、直書きするならアローとかは使えないとおもう。

で、GUIでこうした:

モジュールの終了検知

Choregraphe で、モジュール終了時に ALMemory Raise Event するようにして、それを JavaScript 側でハンドリング。

こうして:

こう:

  session.service("ALMemory").then(ALMemory => {
    ALMemory.subscriber('Shinchokudo/ConvertVideoEnd').then(subscriber => {
      subscriber.signal.connect(() => {
        say('それでは、本日のハイライトです! ご覧ください')
          .then(() => raiseEvent('PlayVideo'))
      })
    })
  })

モジュールの実行結果の受け渡し

モジュール終了時に値を渡す方法がわからなかった(というか時間がなくて調べてない)ので、モジュール内から直接 ALMemory の raiseEvent を実行してもらい、それを JS 側の subscriber で受け取っている。
Square API を叩くモジュールから結果を受け取る時にそうしてる。

python 側でこうして:

ALProxy("ALMemory").raiseEvent("nomidai", earnings)

JS 側でこう:

ALMemory.subscriber('nomidai').then(subscriber => {
  subscriber.signal.connect(price => {
    console.log(`nomidai: ${price}`)
  })
})

HTML からの Ajax 実行

短時間ながら調べた限り、 Ajax の結果を元に Pepper を操作は無理 なんじゃないかなという結論。

まず、 html ディレクトリに index.html を置くやり方だと Ajax が実行できない。ちゃんと調べてないけど、おそらく file:// 的な開き方をしていてセキュリティ制約に引っかかっていると思われる。

次に HTML や JS を外部のサーバに置いて呼び出す方法を検討したが、これもダメ。
単純に Ajax するだけなら可能っぽいが、 Pepper を操作するには Pepper タブレットのローカルに直接置かれてる qimessaging.js (Ver. 2) が必要となる。
こいつを引っこ抜いて外部サーバに置けば……とか考えたが、こいつは pepper との通信に socket.io を使っていて、単体では動作しない。
そこまで調べて、「よく考えたら外部ドメインに置かれた HTML から pepper が操作できるように作るわけなくね?」と気づいて、諦めた。

最終的に、外部の API を叩く Python モジュールを作成して、それを ALMemory によって呼び出すというやり方になった。上記の Square API の件がそれ。

JavaScript で Pepper を喋らせたり移動させたり

こう:

// しゃべる
function say (msg) {
  return session.service("ALTextToSpeech")
    .then(tts => tts.say(msg))
}

// うごく
function moveTo (x, y, theta = 0) {
  return session.service('ALMotion')
    .then(m => m.moveTo(x, y, theta))
}

こんな関数作っとくと、↓こんな感じで繋いで順番に動かせる:

        say('皆さん、顔をふせてください。ルーレットスタート!')
          .then(() => {
            raiseEvent('PlayDrum')
            return moveTo(0, MEMBER * UNIT)
          })
          .then(() => say('フッフッフッ'))
          .then(() => moveTo(0, (1 - MEMBER) * UNIT))
          .then(() => say('さてさて?'))
          .then(() => moveTo(0, (MEMBER - 1) * UNIT))
          .then(() => say('どうかな?'))
          .then(() => moveTo(0, -MEMBER * UNIT))
          .then(() => say('ゴチになるのは?'))
          .then(() => moveTo(0, (target + 1) * UNIT))

移動させてるうちにたまる誤差

このアプリのように、右に左に移動させまくってると、数字上はプラマイゼロの移動をさせていても、だんだんズレが生じてくる。
このズレがそんなに小さくないので気にはなる。今回は妥協したけど。

解決策は特に浮かんでいない。充電スペースに戻るアレも、結構頑張っているっぽい。

まとめ

Pepper 楽しい。
Python や JavaScript で色々できるので、結構手軽に遊ぶことができる。
特に Promise で繋いでサクサク動かすところは書いてて楽しかった。

なお、なかなか苦労したが、このアプリは勝利を掴み、準決勝に進出することができた。
1日目の終わりで最低限のプレゼンのイメージの 70% まではできていて、そこから 300% を目指してギリギリまで機能追加できたのが勝因だと思う。
また、作業の分担が完璧に近いレベルでできてたのも大きかった。これは pepper のモジュール共有の仕組みが素晴らしかったおかげ。「肩を叩く動き」など、細かな動き単位でモジュール化できるのがよい。

MA は 1000% でキメてるやつが優勝することが多いので、ここからどうブラッシュアップするかが課題。