LoginSignup
4
4

More than 3 years have passed since last update.

Alexaにlambda連携させて中野区のゴミの日を教える

Posted at

やりたいこと

Alexaからlambdaを呼び出して中野区のゴミの曜日を返します。
全く触ったことのない人でも一から作れるように書きました。

alexa.png

ALEXAで開発入門してみたので自分なりに纏めてみる
こちらの記事を参考に作ってみました。
細かいところまで説明されていてとても勉強になりましたが、
AWSの仕様変更のためか途中でつまづいたのでその辺りも本稿で記載します。

必要なもの

Alexa Skill Kit

echoから受け取った音声から変数を抽出し、
lambdaへ渡すためのスキルを作成します。

スキルの作成

Amazon developerからAlexa Skills Kitを押下

alexa-Page-3.png

Amazon developer consoleよりスキルの作成を押下

alexa-Page-2.png

以下を入力してスキルを作成を押下

スキル名: TrashDay
デフォルトの言語: 日本語
スキルに追加するモデルを選択: カスタム
スキルのバックエンドリソースをホスティングする方法を選択:
ユーザ定義のプロビジョニング (今回は独自にlambdaにホスティングする)

alexa-Page-4.png

呼び出し名

スキルを呼び出すための呼び出し名を入力

スキルの呼び出し名: テルミーゴミー

「アレクサ、テルミーゴミーを開いて」でスキルが起動する
alexa2-Page-2.png

スロットタイプ

アクションを呼び出すときに発言に含まれる変数を予め用意する。

スロットタイプの横の追加を押下し新規作成。
alexa2-Page-3.png

スロットタイプ名をtypeにしてカスタムスロットタイプを作成を押下
alexa2-Page-4.png

以下のスロット値を追加。
なお、アレクサという値は必須ではない(ちょっとした遊び心※後述)
alexa2-Page-1.png

インテント

呼び出されたスキル(=テルミーゴミー)が実行するアクションを定義する。
今回は、ゴミの曜日を応えるアクションのみ作成する。

インテントの横の追加を押下し新規作成。
alexa2-Page-5.png

インテント名をTellTrashDayとしてカスタムインテントを作成を押下
alexa2-Page-6.png

インテントスロット名をtypeとし、先程作成したスロットタイプtypeを選択
alexa2-Page-7.png

サンプル発話に下記を追加。
なお、{type}の後ろには半角スペースがいるので注意。
alexa2-Page-8.png

以上により、サンプル発話にある発言をしたときに{type}部分が変数として抽出されます。

AWS lambda

Alexaから渡された変数をもとに応答を選択してレスポンスする関数をホスティングします。

関数の作成

AWSマネジメントコンソールからlambdaの画面へ遷移し、関数の作成を押下
alexa2-Page-9.png

AWSで提供されるalexa-skills-kit-color-expert-pythonを選択
Alexaが好きな色を聞いてくるサンプルアプリケーションが組み込まれています。
alexa2-Page-10.png

アプリケーション名とTopicNameParameterをTrashDayにしてデプロイを押下
TopicNameParameterはCloudFormationで渡されるパラメーターに格納されますが、
特に関数内で使用されている形跡はないので任意の値で大丈夫です。
alexa2-Page-11.png

TrashDay.pyをデプロイ

左メニューから関数を選択し先程のアプリケーション名が記載さている関数を押下
alexa2-Page-12.png

ラムダ関数を選択して、下画面のエディタをTrashDay.pyの内容に置換
alexa2-Page-13.png

TrashDay.py
# coding:utf-8
"""
This sample demonstrates a simple skill built with the Amazon Alexa Skills Kit.
The Intent Schema, Custom Slots, and Sample Utterances for this skill, as well
as testing instructions are located at http://amzn.to/1LzFrj6

For additional samples, visit the Alexa Skills Kit Getting Started guide at
http://amzn.to/1LGWsLG
"""

from __future__ import print_function


# --------------- Helpers that build all of the responses ----------------------

### 各インテントを処理し終わった後にAlexaに戻すために使う関数
def build_speechlet_response(title, output, reprompt_text, should_end_session):
    return {
        'outputSpeech': {
            'type': 'PlainText',
            'text': output
        },
        'card': {
            'type': 'Simple',
            'title': "SessionSpeechlet - " + title,
            'content': "SessionSpeechlet - " + output
        },
        'reprompt': {
            'outputSpeech': {
                'type': 'PlainText',
                'text': reprompt_text
            }
        },
        'shouldEndSession': should_end_session
    }

### build_speechlet_responseで作ったJSONを更に返答用のJSONに格納して返す関数
def build_response(session_attributes, speechlet_response):
    return {
        'version': '1.0',
        'sessionAttributes': session_attributes,
        'response': speechlet_response
    }


# --------------- Functions that control the skill's behavior ------------------

def get_welcome_response():
    """ If we wanted to initialize the session to have some attributes we could
    add those here
    """

    session_attributes = {}
    card_title = "Welcome"
    speech_output = "やぁ、こんにちは、テルミーゴミーだよ、 " \
                    "捨てたいゴミの指定曜日を応えるよ! " 

    # If the user either does not reply to the welcome message or says something
    # that is not understood, they will be prompted again with this text.
    reprompt_text = "捨てたいゴミの指定曜日を応えるよ! " 

    should_end_session = False
    return build_response(session_attributes, build_speechlet_response(
        card_title, speech_output, reprompt_text, should_end_session))


def handle_session_end_request():
    card_title = "Session Ended"
    speech_output = "またきてね " 

    # Setting this to true ends the session and exits the skill.
    should_end_session = True
    return build_response({}, build_speechlet_response(
        card_title, speech_output, None, should_end_session))


# --------------- Events ------------------

def on_session_started(session_started_request, session):
    """ Called when the session starts """

    print("on_session_started requestId=" + session_started_request['requestId']
          + ", sessionId=" + session['sessionId'])


def on_launch(launch_request, session):
    """ Called when the user launches the skill without specifying what they
    want
    """

    print("on_launch requestId=" + launch_request['requestId'] +
          ", sessionId=" + session['sessionId'])
    # Dispatch to your skill's launch
    return get_welcome_response()

### 発言に対応するインテントを判定し各関数に分岐する関数
def on_intent(intent_request, session):
    """ Called when the user specifies an intent for this skill """

    print("on_intent requestId=" + intent_request['requestId'] +
          ", sessionId=" + session['sessionId'])

    intent = intent_request['intent']
    intent_name = intent_request['intent']['name']

    # Dispatch to your skill's intent handlers

    ### TellTrashDayを含む各インテントに分岐
    if intent_name == "TellTrashDay":
        return set_TellTrashDay_text(intent, session)
    elif intent_name == "AMAZON.HelpIntent":
        return get_welcome_response()
    elif intent_name == "AMAZON.CancelIntent" or intent_name == "AMAZON.StopIntent":
        return handle_session_end_request()
    else:
        raise ValueError("Invalid intent")


def on_session_ended(session_ended_request, session):
    """ Called when the user ends the session.

    Is not called when the skill returns should_end_session=true
    """
    print("on_session_ended requestId=" + session_ended_request['requestId'] +
          ", sessionId=" + session['sessionId'])
    # add cleanup logic here


# --------------- Main handler ------------------
### lambdaが呼ばれた時に一番最初に実行される関数
### eventにはlambdaへのリクエスト情報が格納される
def lambda_handler(event, context):
    """ Route the incoming request based on type (LaunchRequest, IntentRequest,
    etc.) The JSON body of the request is provided in the event parameter.
    """
    # get applicationId from request.json
    print("event.session.application.applicationId=" +
          event['session']['application']['applicationId'])

    """
    Uncomment this if statement and populate with your skill's application ID to
    prevent someone else from configuring a skill that sends requests to this
    function.
    """
    # if (event['session']['application']['applicationId'] !=
    #         "amzn1.echo-sdk-ams.app.[unique-value-here]"):
    #     raise ValueError("Invalid Application ID")

    if event['session']['new']:
        on_session_started({'requestId': event['request']['requestId']},
                           event['session'])
    ### スキル名のみの呼び出しで、インテントが含まれていないとき実行される
    if event['request']['type'] == "LaunchRequest":
        return on_launch(event['request'], event['session'])
    ### ユーザがインテントに対応する発言をしたとき実行される
    elif event['request']['type'] == "IntentRequest":
        return on_intent(event['request'], event['session'])
    ### 終了時、応答可能なインテントがないとき、エラー時に実行される
    elif event['request']['type'] == "SessionEndedRequest":
        return on_session_ended(event['request'], event['session'])


### スロットタイプに一致するゴミの曜日を返す関数
def set_TellTrashDay_text(intent, session):
    """ Sets the color in the session and prepares the speech to reply to the
    user.
    """

    card_title = intent['name']
    session_attributes = {}
    should_end_session = False
    speech_output = "早く言えよ、お前をクズカゴに捨てたっていいんだぞ"

    ### 燃えるゴミリスト
    burn_list = ['燃える', '生', '紙くずの']
    ### 燃えないゴミリスト
    notburn_list = ['燃えない', 'プラスチックの', 'プラの', 'プラ']
    ### アルミ缶系ゴミリスト
    can_list = ['アルミ缶の', 'アルミ缶', '空き缶の', 'ペットボトルの', 'ビンの', '缶の', '缶', 'スチール缶の']
    ### ダンボールゴミリスト
    paper_list = ['ダンボールの', '新聞紙の', '古紙の', '紙の', '紙']
    ### 危険物系ゴミリスト
    glass_list = ['ガラスの', '金属の', '陶器の']
    ### 言ったらやばいやつ
    alexa_list = ['アレクサの', 'アレクサ']

    if 'type' in intent['slots']:
        ### 3rd part was 'name' so changed to 'value'
        trash_type = intent['slots']['type']['value'] 

        if trash_type in burn_list:
            speech_output = '火曜日と金曜日です'
        elif trash_type in notburn_list:
            speech_output = '月曜日です'
        elif trash_type in can_list:
            speech_output = '水曜日です'
        elif trash_type in paper_list:
            speech_output = '月曜日です'
        elif trash_type in glass_list:
            speech_output = '土曜日です'
        elif trash_type in alexa_list:
            speech_output = '宇宙ゴミになりたいようだな'
    else:
        speech_output = "すみません、ちょっと何言ってるか分からないです"

    print(speech_output)
    reprompt_text = speech_output
    return build_response(session_attributes, build_speechlet_response(
        card_title, speech_output, reprompt_text, should_end_session))

トリガーとなるAlexa Skills Kitを指定

トリガーを追加を押下
alexa2-Page-14.png

一旦、Alexa developer consoleに戻り、エンドポイントからスキルIDをコピー
alexa2-Page-16.png

AWSに戻り、Alexa Skills Kitを選択してコピーしたスキルIDをペーストし追加を押下
alexa2-Page-15.png

正常に追加されたらlambda側の設定は完了です。あと一息。

エンドポイントにlambdaを指定

最後に、先程作成したlambda関数をAlexa側で指定します。

AWSのlambdaの画面上部にあるARNをコピー
alexa2-Page-18.png

Alexaのエンドポイントのデフォルトの地域にペースト
alexa2-Page-17.png

エンドポイントの保存を押下

以上で全ての設定が完了したので、モデルを保存してモデルをビルドします。
alexa2-Page-19.png

テスト

alexa developer consoleのテストで実際に動作するか確認します。

スキルテストが有効になっているステージを開発中に変更
alexa2-Page-20.png

スキルを起動して、インテントに追加したアクションを試します。
alexa2-Page-21.png

動きました!!

ユーモアもバッチリです。
alexa2-Page-22.png

ちなみに、スキルをストアで公開しなくても、
自分のechoでなら何も設定しなくてもスキルを使えました。
(ブラウザ上でテストしていたら、うちのechoが反応してびっくりしました笑)
Amazonアカウントで自動的に連携されているみたいですね。

トラブルシューティング

lambda関数内に日本語があるとエンコードエラー

コードの先頭に# coding:utf-8を付加し、日本語を読み込めるようにしています。

うまく動作しないときは

Alexaのテスト画面にJSON入力が表示されるのでlambdaのテストにコピーして試してみることで、Alexaとlambdaのどっちに原因があるのか切り分けることができます。

4
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
4