PepperがSlackにお知らせしてくれるようにしたよ

By |10月 21, 2015|pepper, |


今日、2015年10月21日は、映画「バック・トゥ・ザ・フューチャー」で主人公のマーティーがやってきた「未来」です。

あの頃想像していた未来の技術は、実現したものもあれば、そうでないものもあるし、想像していなかったものが存在していたりもする。
空想でも描かれなかったような技術が生まれているという事実に
「遠い未来のような気がしていた21世紀に生きているのだなぁ」と技術職としては胸が熱くなります。

ところで、pepperが受付でお客様をご案内したり社員を呼び出したりしてくれたら21世紀という感じがしないだろうか?私はしちゃうな。
というわけで「pepper受付プロジェクト」を発足し、第一弾プログラムを作ってみました。

スケッチ

どんなものを作りたいか、理想を描いてみた。夢は大きくボーイズ・ビー・アンビシャスだ。

理想

理想

・お客様、来社
・ご挨拶をして、誰に会いに来たかを尋ねる
・「お客様がいらしてますよ」と社員に連絡
・お待ちくださいねとご挨拶(そしてお待ちいただいている間に歓談)

「お待ち下さいね」と言ってpepperが移動して該当社員を顔認識して本人をひっぱってきたら超未来ですが
握力2kgのpepperくんには、まだじっとしていてもらいましょう。

実現するには課題がある

・音声認識で「やまだ」と「はまだ」を間違える等の可能性が多いにある
・まだpepperくんは、会話がちょっと下手なの…

ゆらぎを吸収し、それっぽい名前の人リストと、会議室の予約状況データを取得し、この時間帯の打ち合わせ参加者のリストを作成しタブレットに表示して選んでもらう等の解決法はあるけれど
今回はもっとシンプルに行こう。第一弾だし。

現実を見て設計

現実

現実

受付で人を感知すると写真を撮影し、slackに通知する。
実際は撮影において許諾を得る必要があるので手直しは必須になりますが、受付に出る前に社内で練習をしてもらうために
最低限シンプルな機能ですすめていきます。Done is better than perfect!

このような流れで作ります。

黄色は既存ボックスを利用して、ピンクはpythonボックスで作る。

黄色は既存ボックスを利用して、ピンクはpythonボックスで作る。

本当は撮影した画像をそのままslackにupしたかったのですが、botはfileuploadのAPIを叩けない様子だったので
とりあえず一回S3にUPしてURLを添付するという形でやってみることに。
slackAPIを触るのは初めてなので(というかslack自体初めて)もしかしたらもっとステキなやり方があるのかもしれない。

いざ作成

pythonで書けるので、パッケージを使える所は使っていきます。
choregrapheの「プロジェクトの内容」に表示されているディレクトリツリーに「lib」ディレクトリを追加して
そこに「フォルダのインポート」でライブラリを保存していきます。
以下のパッケージを使いました。

  • boto
  • slack_client
  • websocket

Take Pictureボックス

既存のボックスに少し手を入れるだけ
ここの処理はアルデバランアトリエさんのQiita記事を参考にしました。(http://qiita.com/Atelier-Akihabara/items/833e49a079788af55afb
イメージ図ではtake pictureボックスからS3ボックスに写真をパラメーターで渡していますが
ここはいっそ決め打ちで進めます。
libディレクトリと同様に「html」ディレクトリを作成し、ダミーの「image.jpg」を設置しておきます。

[html]
class MyClass(GeneratedClass):
def __init__(self):
GeneratedClass.__init__(self, False)
self.resolutionMap = {
‘160 x 120’: 0,
‘320 x 240’: 1,
‘640 x 480’: 2,
‘1280 x 960’: 3
}
self.cameraMap = {
‘Top’: 0,
‘Bottom’: 1
}

self.recordFolder = "/home/nao/recordings/cameras/"

def onLoad(self):
self.framemanager = ALProxy("ALFrameManager")
self.bIsRunning = False
try:
self.photoCapture = ALProxy( "ALPhotoCapture" )
except Exception as e:
self.photoCapture = None
self.logger.error(e)

def onUnload(self):
pass

def onInput_onStart(self):
# アプリのルート/html/に画像を保存したいのでディレクトリを指定
import os
self.recordFolder = os.path.join(self.framemanager.getBehaviorPath(self.behaviorId), "../html")

if( self.bIsRunning ):
return
self.bIsRunning = True
resolution = self.resolutionMap[self.getParameter("Resolution")]
cameraID = self.cameraMap[self.getParameter("Camera")]
fileName = self.getParameter("File Name")
if self.photoCapture:
self.photoCapture.setResolution(resolution)
self.photoCapture.setCameraID(cameraID)
self.photoCapture.setPictureFormat("jpg")
self.photoCapture.takePicture( self.recordFolder, fileName )
self.bIsRunning = False
self.onStopped()
[/html]

S3postボックス(pythonボックス)

pepper大好き社内ペパンジェリストかつAWS大好きマンとしては、一度はpepperからAWSに接続しておきたかったので、正直わざわざS3を介しました。
新規pythonボックスを作成して、Access KeyとSecret Access Key用の新規変数を追加。(typeは文字列)
中途半端にここだけ変数にしたけど、bucketとかもここで変数にすると後々汎用性があっていいんじゃないかしら。

slack_s3box_addvar

するとこんな感じになるのでIAMで作ったS3アクセス用ユーザーのクレデンシャルを入力する。
slack_s3box_config

保存後にS3のURLを次のボックスにお伝えしたいので、onStoppedのTypeを文字列にしておく。
するとboxのonStoppedの出力端子(って呼び方でいいのかな)が青色になって、終了時に文字列を出力できるようになります。
入出力の色は、黒がバン、黄色が数字、青が文字列なのです。パターン青、文字列です!
slack_s3box_out

ボックスのコードはこんな感じ。
[html]
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):
# libディレクトリから読み込み
import sys, os, time
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):
if moduleName in sys.modules:
self.logger.info("Loaded: %s, %s" % (moduleName, sys.modules[moduleName].__file__))
reload(sys.modules[moduleName])

rootdir = os.path.join(self.framemanager.getBehaviorPath(self.behaviorId), "../html")
# 読み込む画像file名(take photoで撮影してアプリルートのhtml以下に保存したお写真)
load_filename = "image.jpg"
# S3に保存する際の名前は接頭語+時間にしておいた
save_filename = "image_"+str(time.time())+".jpg"

from boto.s3 import connection
from boto.s3.key import Key

# ボックスに追加した変数を取得する
ACCESS_KEY_ID = self.getParameter("AccessKey")
SECRET_ACCESS_KEY = self.getParameter("SecretAccessKey")
BUCKET_NAME = "< 使用するBUCKET>"

conn = connection.S3Connection(ACCESS_KEY_ID, SECRET_ACCESS_KEY)
bucket = conn.get_bucket(BUCKET_NAME)

k = Key(bucket)
k.key = save_filename
k.set_contents_from_filename(rootdir + "/" + load_filename)

# UPしたファイルがslackから閲覧できるように公開にしておく
k.make_public()

s3Url = k.generate_url(expires_in=0, query_auth=False)
# 終了の通知に保存したS3ファイルのURLを持たせる
self.onStopped(s3Url)

def onInput_onStop(self):
self.onUnload()
self.onStopped()
[/html]

Slackボックス

slackボックスのonStartも文字列を受け取れるように変更しておきます。
slack_slackbox_in
ボックスのコードはこんな感じ。
[html]
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, s3Url):
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):
if moduleName in sys.modules:
self.logger.info("Loaded: %s, %s" % (moduleName, sys.modules[moduleName].__file__))
reload(sys.modules[moduleName])

self.logger.info("Slack送信中!… File: %s" % s3Url)

import json
from slackclient import SlackClient

post_text = "Slackに送信中です"
token = "<slack のtoken>"
channel = "<slack のチャンネル>"
user = "<bot のユーザー名>"
sc = SlackClient(token)

att = [{
"color": "#36a64f",
"title": "お客さんが来ました(仮)",
"title_link": s3Url,
"text": "rafikiは12月に向けてがんばっています",
"image_url": s3Url
}]

sc.api_call("chat.postMessage", attachments=json.dumps(att), channel=channel, username=user, as_user=user, text=post_text)

self.onStopped()

def onInput_onStop(self):
self.onUnload()
self.onStopped()
[/html]
以上のボックスをこんな感じにつなげます
slack_choregraphe_view

すると、pepperがslackにお知らせしてくれます
slack_post
※rafikiというのは弊社でのpepperの名前です。忙しい年末までには受付デビューできるようにがんばってくれています。

やってみて悩んだこと

実機テスト時のファイルのやりとり

アルデバラン・アトリエでワークショップを受講した際に、スタッフさんも「動画ファイルとかテストする時は大変です」と言っていたけど
全ファイルをテスト実行ディレクトリ(/home/nao/.local/share/PackageManager/apps/.lastUploadedChoregrapheBehavior/)から削除→再度全ファイルUPという感じなので
ライブラリとか使っていると、たいへんに重たいのです。

デバッグ/ログ出力

self.logger.info()でバーチャルロボットはログを確認できたけど、pepper実機では全然出力されませんでした。
重大なエラーの場合boxが赤くなってマウスオーバーするとエラーメッセージが見られたけど、そうでもない時は
うんともすんとも言わないpepperをドキドキしながら見つめてはコードをチェックしていました。
アトリエのワークショップで、タブレットのログの出し方は教わったけど、他はどう出したらいいのかな〜と模索中。

これからやりたいこと

  • やはりS3に全公開UPだとよろしくないので画像の取り扱いを考える。
  • タブレットでのナビゲーションを考える。呼び出す人の選択、写真撮影の可否など。
  • 撮影時のアクションを考える。とりますよー!3,2,1!みたいな感じで撮影してもらいたい。シャッター音もあったほうがいいかな。
  • パッケージ化というのか、あのタブレットからタップして起動できる形のアプリを作りたい

というわけで第一弾はこの状態で社内の人達に見てもらって修正を行って行きます。
pepperが弊社の受付に立つその日のためにがんばるぞ〜!