はじめに
前回に続いてAmazon Echoで作ってみた系の投稿です。
我が家では色々IoT化を進めていますが、今回は禁断の嫁のIoT化に挑戦してみました。
前回:Amazon Echoにバスの到着時刻を教えて貰う
https://qiita.com/osa9/items/e2cc6318b7ed736ac6ff
今回のサンプルコードはこちら
https://github.com/osa9/alexa-line/
つくるもの
Alexaとの対話は基本的にルールベースなので、そこに意思は介在しません。そこで、応答に人間の意思を入れる事が出来れば面白いかもと思って、Amazon Echoを介して嫁とLINEチャット出来るスキルを作成してみました。
作成物のイメージはこんな感じです。(若干構成図含む)
Amazon Echoに「焼き肉食べたい」等のメッセージを話しかけると嫁のLINEに送信されて、嫁からの応答をAlexaが教えてくれます。
仕組み
嫁のLINEへのメッセージ送信にはLINE Bot(Messaging API)を利用します。自分と嫁、LINE Botの居るグループを作ってそこでやりとりすれば後から履歴も確認出来て便利です。
課題はAlexaに嫁からの回答をしゃべらせる部分です。APIと違って奥さんは数百msで応答してくれるわけではないので、応答があった時にメッセージをプッシュしてしゃべらせたい所ですが、最近Alexaに実装されたプッシュ通知機能はLEDや効果音での通知だけで直接しゃべらせる事は出来ないようです。リアルタイムで会話しているような雰囲気にしたかったので、今回は嫁をポーリングして、反応があるまで待つ実装にしました。
Amazon Echoからの呼び出しの応答タイムアウトを計測してみたら、恐らく40〜60秒ぐらいでリトライされるようでした。なので少し短かいですが30秒をタイムアウトとしました。30秒を過ぎたら自分でLINEを見に行く事とします。(「反応来てる?」で未読メッセージを読み上げる機能を実装しても良さそうですね)
ただ、ポーリング方式の場合、最大で30秒Alexaが無反応になってしまうので、ちゃんとスキルが起動したか分からないのが気になる所です。その部分については最近実装されたProgressive Responseを使って、メッセージ送信後にAlexaにメッセージ送信が完了したとしゃべらせるようにしてみます。
Progressive Response
先月に追加された機能です。
ローディングや嫁の応答待ち等で時間がかかる処理を行う際に、途中経過をAlexaにしゃべらせる事の出来る機能です。Alexaからリクエストが来てLambdaが応答を返すまでの間だけ、特定のURLにリクエストする事でAlexaにメッセージをしゃべらせる事が出来ます。
Send the User a Progressive Response
https://developer.amazon.com/ja/docs/custom-skills/send-the-user-a-progressive-response.html
ポイントは以下の通りです。
- Lambdaが起動されてから応答を返すまでの間だけ利用する事が出来る
- Progressive Responseは1回の会話につき5回まで
- 実機でのみ利用可能(シミュレーターではapiEndpoint情報がセットされない)
Pythonでのサンプルコードです。
import requests
import json
import time
def progressive_response(event, message):
endpoint = event['context']['System'].get('apiEndpoint')
access_token = event['context']['System'].get('apiAccessToken')
request_id = event['request']['requestId']
# エンドポイント情報が無い時(シミュレーター等)
if not endpoint or not access_token:
print('No Endpoint')
return
response = {
"header": {
"requestId": request_id
},
"directive": {
"type": "VoicePlayer.Speak",
"speech": message
}
}
res = requests.post(
endpoint + '/v1/directives',
headers={
'Authorization': 'Bearer {}'.format(access_token),
'Content-Type': 'application/json'
},
data=json.dumps(response))
if res.status_code != 200 and res.status_code != 204:
print('Progressive Response Failed (status_code={})'
.format(res.status_code))
def alexa_endpoint(event, context):
# 一時応答を返す
progressive_response(event, 'ちょっとまっててね')
time.sleep(5)
# 5回までOK
progressive_response(event, 'まだ早い')
time.sleep(5)
# Alexaに普通に応答を返す
response = {
'version': '1.0',
'response': {
'outputSpeech': {
'type': 'PlainText',
'text': "完了",
}
}
}
return response
ちなみに、node.jsなら公式ライブラリを使えば一瞬です。
https://github.com/alexa/alexa-skills-kit-sdk-for-nodejs/blob/master/Readme.md#directive-service
LINEの応答をしゃべらせる
スキルが呼び出されるとLambdaが起動されて、Lambda内で処理を完結して応答に返答メッセージを含める必要があります。しかし、LINEの返答はWebHookなので、返答の際に別のLambdaが起動されるため相性が悪いです。今回はLINEからWebHook通知が来たら結果をDynamoDBに格納し、元のLambdaからポーリングして取得するという構成にしました。
LINEのテンプレートメッセージを使うと、PostBackの中に好きなデータ(DBのキーとか)を含められるのでデータを取り回ししたい時には便利です。
def line_send_message(to, key, message):
# message [はい][いいえ] みたいな感じのメッセージ
line_message = TemplateSendMessage(
alt_text=message,
template=ConfirmTemplate(
text=message,
actions=[
PostbackTemplateAction(
label='はい',
text='はい',
data=json.dumps({'id': key, 'message': 'はい'})
),
PostbackTemplateAction(
label='いいえ',
text='いいえ',
data=json.dumps({'id': key, 'message': 'いいえ'})
)
]
)
)
linebot.push_message(to, line_message)
デモ
実際にうちの奥さんとやりとりした例です。Amazon Echoに話しかけて、奥さんが30秒以内に返答してくれればAlexaがしゃべってくれます。
おわりに
今回はAmazon Echoを介して嫁のやりとりを自動化しました。実際に作ってみると寝転びながら奥さんと会話出来るので結構実用的でした(許されるのかは別として)。相手の反応をAlexaが教えてくれるというのかなかなか新鮮で面白かったです。
今回の仕組みを応用してAmazon Echo同士で会話させたりも出来そうですね。
ちなみに、デモでは晩御飯をリクエストしていますが、実際は晩御飯を作るのは8割がた自分です。