ガジェットからカスタムイベントを受信する



ガジェットからカスタムイベントを受信する

カスタムスキルを有効にして、ガジェットからの情報を処理して対応できるようにするには、ガジェットからスキルにカスタムイベントが送信されるよう設定します。カスタムイベントには、ガジェット固有の任意のペイロードが含まれます。

カスタムイベントをサポートするには、まずカスタムインターフェースを定義し、次にガジェットのファームウェアで、定義したカスタムイベントをエンコードできるコードを追加します。スキルコードはカスタムインターフェースコントローラーを使用することで、これらのカスタムイベントを受信できます。このインターフェースは、Alexa Skills Kit開発者コンソールまたはAlexa Skills Kitコマンドラインインターフェース(ASK CLI)を使用してスキルをセットアップする際に、スキルに追加します。

このトピックでは、カスタムイベントを受信するようスキルを設定する方法について説明します。カスタムイベントは、このトピックでは単に「イベント」と呼びます。インターフェースを定義する方法や、そのインターフェースをサポートするためにガジェットで行うべき処理については、カスタムインターフェースの詳細を参照してください。また、スキルからガジェットにカスタムディレクティブを送信することもできます。詳しくは、スキルからガジェットにカスタムディレクティブを送信するを参照してください。

サンプルコードについては、GitHubのAlexa Gadgets Raspberry Piサンプルを参照してください。

概要

ガジェットはAlexaクラウドに直接接続されないため、Echoデバイスを使用してスキルとの通信を処理します。次の図は、ガジェットがEchoデバイスを通じてスキルにイベントを送信する流れを示しています。

Alexa Gadgetからスキルにカスタムイベントを送信する

カスタムイベントのしくみ

スキルはガジェットからイベントを自動的に受信するわけではありません。イベントの受信を求めていること、受信するイベントのタイプ、イベントが満たす必要がある条件、Alexaからスキルにイベントを転送する期間を、スキルからAlexaに示す必要があります。そのためには、スキルからAlexaに対して、この情報を使用してイベントハンドラーのコンフィギュレーションを行うディレクティブを送信します。

ガジェットのイベントによってスキルの起動がトリガーされることはありません。スキルは既に動作中で、イベントハンドラーを開始するディレクティブを送信済みである必要があります。これは、スキルでガジェットからのイベントを受信する準備ができていることを意味します。

各スキルインスタンスに対して一度に有効化できるイベントハンドラーは1つのみです。新しいイベントハンドラーを開始すると、それが前のイベントハンドラーと置き換わります。通常、イベントハンドラーはスキルの動作に合わせて何度も開始と停止を繰り返します。

前提条件

ガジェットからのカスタムイベントを受信するためのスキルコードを追加する前に、以下を実行します。

  • Echoデバイスのソフトウェアバージョンが、開始する前にに記載されているバージョン以降であることを確認します。Echoデバイスのソフトウェアバージョンを確認するには、Alexaアプリで設定デバイスの設定>デバイス>その他デバイスのソフトウェアバージョンの順に移動します。Echoデバイスのソフトウェアバージョンを最新にするには、以下を実行します。
    • 画面の付いていないEchoデバイスの場合 – 「アレクサ、ソフトウェアをアップデートして」と呼びかけます。
    • 画面付きEchoデバイスの場合 – Echoデバイスで設定デバイスオプションソフトウェアアップデートの確認の順に選択します。
  • インターフェースが定義されており、ガジェットがそのインターフェースのサポートを宣言していることを確認します。これらの要素の詳細については、カスタムインターフェースの詳細を参照してください。
  • カスタムスキルを作成し、スキルのサポート対象インターフェースにカスタムインターフェースコントローラーのインターフェースを追加します。詳しくは、ガジェットにスキルを設定するを参照してください。

カスタムイベントのディレクティブ

イベントハンドラーに関連するディレクティブは2つあります。

  • CustomInterfaceController.StartEventHandler – スキルにイベントを渡すイベントハンドラーのコンフィギュレーションと開始を行うコマンドをAlexaに送信します。できるだけ早くスキルがイベントを受信できるようにするには、AlexaからのLaunchRequestへの応答として、このディレクティブを送信することをお勧めします。
  • CustomInterfaceController.StopEventHandler – 現在のイベントハンドラーを停止するコマンドをAlexaに送信します。この結果、スキルへのイベントの送信が停止されます。これはオプションです。このディレクティブを送信しない場合、指定された期間が経過すると、イベントハンドラーは自動的に停止します。

StartEventHandlerディレクティブ

このディレクティブは、イベントハンドラーのコンフィギュレーションと開始を行います。これにより、スキルがカスタムイベントを受信できるようになります。1つのスキルに対して一度に有効化できるイベントハンドラーは1つのみです。

以下は、StartEventHandlerディレクティブの例と、SDKを使用してディレクティブを組み立てる方法を示しています。例の後に、各フィールドについて説明します。

クリップボードにコピーされました。

{
    "outputSpeech": {
        "type": "SSML",
        "ssml": "<speak>ゲームを始めましょう。</speak>"
    },
    "shouldEndSession": false,
    "directives": [
        {
            "type": "CustomInterfaceController.StartEventHandler",
            "token": "1234abcd-40bb-11e9-9527-6b98b093d166",
            "expiration": {
                "durationInMilliseconds": 8000,
                "expirationPayload": {
                    "gameOverSpeech": "ゲームオーバーです。 ステータスを聞きたいですか?"
                }
            },
            "eventFilter": {
                "filterExpression":{
                    "and": [
                        {"==": [{"var": "header.namespace"}, "Custom.Robot"]},
                        { "==": [{ "var": "endpoint.endpointId" }, "amzn1.ask.endpoint.ABCDEFGHIJKLMNOPQRSTUVWXYZ"]}
                    ]
                },
                "filterMatchAction": "SEND_AND_TERMINATE"  
            }
        }
    ]
}

クリップボードにコピーされました。

このサンプルコードは、Alexa Skills Kit SDK for Node.js(v2)を使用しています。

const StartInputIntentHandler = {
  canHandle(handlerInput) {
    return handlerInput.requestEnvelope.request.type === 'IntentRequest'
      && handlerInput.requestEnvelope.request.intent.name === 'StartEventHandlerIntent';
  },
  handle(handlerInput) {

      // イベントハンドラーのコンフィギュレーションを行うディレクティブを含む
      // 応答を作成します。ここに示されているディレクティブは、
      // イベントを8秒間リッスンし、特定のガジェットから送信される
      // Custom.Robot名前空間に属するイベントのみを受信するよう
      // イベントハンドラーのコンフィギュレーションを行います。このコンフィギュレーションでは、
      // 条件を満たすイベントを受信したらイベントハンドラーを停止
      // することも指定します。
      const response = handlerInput.responseBuilder
          .speak("ゲームを始めましょう。")
          .withShouldEndSession(false)
          .addDirective({
            'type': 'CustomInterfaceController.StartEventHandler',
            'token': '1234abcd-40bb-11e9-9527-6b98b093d166',
            'expiration': {
                'durationInMilliseconds': 8000,
                'expirationPayload': {
                  'gameOverSpeech': 'ゲームオーバーです。 ステータスを聞きたいですか?'
                }
            },
            'eventFilter': {
                'filterExpression':{
                    'and': [
                        {'==': [{'var': 'header.namespace'}, 'Custom.Robot']},
                        {'==': [{'var': 'endpoint.endpointId' }, 'amzn1.ask.endpoint.ABCDEFGHIJKLMNOPQRSTUVWXYZ']}
                    ]
                },
                'filterMatchAction': 'SEND_AND_TERMINATE'
            }
         })
          .getResponse();

         // CloudWatchへの応答を記述します。
         console.log("===応答=== "+ JSON.stringify(response));

         // 応答を返します。
         return response;

   }
};

クリップボードにコピーされました。

このサンプルコードはAlexa Skills Kit SDK for Javaを使用しています。

@Override
public Optional<Response> handle(HandlerInput input) {

    final String speechText = "ゲームを始めましょう。";

    // リクエストIDをトークンとして使用し、セッションアトリビュートでこの値をキャッシュします。
    String token = input.getRequest().getRequestId();
    input.getAttributesManager().getSessionAttributes().put(StatusGaugeHandler.TOKEN_KEY, token);

    // スキルでカスタムイベントを受信できるようにする
    // StartEventHandlerディレクティブを作成します。スキルで一度に
    // 有効化できるイベントハンドラーは1つのみです。
    StartEventHandlerDirective directive = StartEventHandlerDirective.builder()
        .withToken(token)
        .withEventFilter(EventFilter.builder()
                .withFilterExpression(JsonUtil.fromString(FILTER))
                .withFilterMatchAction(FilterMatchAction.SEND).build())
        .withExpiration(Expiration.builder()
            .withDurationInMilliseconds(30000l)
            .withExpirationPayload(JsonUtil.fromString(EXPIRATION_PAYLOAD)).build())
        .build();

    Optional<Response> response = input.getResponseBuilder()
            .withSpeech(speechText)
            .withSimpleCard(RobotEventHandler.SKILL_TITLE, speechText)
            .addDirective(directive).build();

    System.out.println("=== 返される応答 === " + JsonUtil.asString(response.get()));
    return response;
}

クリップボードにコピーされました。

このサンプルコードはAlexa Skills Kit SDK for Pythonを使用しています。

def build_start_event_handler_directive(token, duration=30000):
    """イベントハンドラーを開始するカスタムディレクティブを作成します。

    :param token: イベントハンドラーに関連付けられる一意のID
    :param duration: イベントハンドラーの有効期間(ミリ秒)
    :return: イベントハンドラーを開始するカスタムディレクティブ
    """

    filter_expression = {"==": [{"var": "header.namespace"}, "Custom.Event"]}

    payload = {"data": "スキルセッションの有効期限が切れました。さようなら。"}
    return StartEventHandlerDirective(
        token=token,
        event_filter=EventFilter(
            filter_expression=filter_expression,
            filter_match_action=FilterMatchAction.SEND
        ),
        expiration=Expiration(
            duration_in_milliseconds=duration,
            expiration_payload=payload))

StartEventHandlerディレクティブには、次のフィールドが含まれています。

フィールド 説明 必須

type

ディレクティブの種類です。CustomInterfaceController.StartEventHandlerに設定する必要があります。

文字列

token

イベントハンドラーによってディスパッチされたすべてのカスタムイベントと関連付けるための一意のIDです。このトークンを保存しておき、新しいイベントハンドラーを開始した後で送られてくるイベントを拒否するフィルターとして使用します。AlexaからスキルへのリクエストのrequestIdを使用できます。

UUID16の文字列

expiration

イベントハンドラーの期限と、イベントハンドラーが期限切れになった場合にのみスキルが受信するJSONペイロード(オプション)を定義するオブジェクトです。

オブジェクト

expiration.durationInMilliseconds

接続されたガジェットからのイベントがスキルに渡されるまでの時間(ミリ秒)です。この期限が切れるまで、またはイベントハンドラーが何らかの理由で停止するまで、スキルはイベントを受信し続けます。
最小値: 1,000(1秒)です。
最大値: 90000(90秒)です。

int64

expiration.expirationPayload

イベントハンドラーが期限切れになった場合にのみスキルが受信する自由形式のJSONオブジェクトです。
最大サイズは 8,000文字です。

オブジェクト

eventFilter

jsonlogicイベントフィルター式と、それに対応する一致アクションを定義します。このフィルターは、イベントハンドラーの期間中、すべてのイベントに適用されます。フィルター式によって却下されたイベントは、スキルに送信されません。

オブジェクト

filterExpression

JSONロジックを表すJSONオブジェクトです。これに照らしてイベントが評価されます。この式を満たすと、対応するmatch(一致)アクションが実行されます。

例については、フィルター式の例を参照してください。

オブジェクト(jsonlogic

filterMatchAction

フィルター式が一致すると、アクションを実行します。有効な値は、SENDSEND_AND_TERMINATEです。SENDアクションは、フィルター式に一致するすべてのカスタムイベントをスキルに送信します。SEND_AND_TERMINATEアクションは、フィルター式に一致するカスタムイベントをスキルに送信してから、イベントハンドラーを終了します。このアプローチによる終了は、期限切れペイロードをトリガーしません。

列挙{SEND, SEND_AND_TERMINATE}

フィルター式の例

StopEventHandlerディレクティブ

このディレクティブは、提供されたトークンに関連するイベントハンドラーが実行中である場合に、イベントハンドラーを停止します。イベントハンドラーの期限が切れる前にスキルによってこのディレクティブが送信された場合、スキルにはCustomInterfaceController.Expiredイベントが送信されません。StopEventHandlerディレクティブの例を次に示します。

クリップボードにコピーされました。

{
    "shouldEndSession": false,
    "outputSpeech": {
        "type": "SSML",
        "ssml": "<speak>ゲームオーバーです。</speak>"
    },
    "directives": [
        {
            "type": "CustomInterfaceController.StopEventHandler",
            "token": "1234abcd-40bb-11e9-9527-6b98b093d166"
        }
    ]
}

クリップボードにコピーされました。

このサンプルコードは、Alexa Skills Kit SDK for Node.js(v2)を使用しています。

const StopInputIntentHandler = {
  canHandle(handlerInput) {
    return handlerInput.requestEnvelope.request.type === 'IntentRequest'
      && handlerInput.requestEnvelope.request.intent.name === 'StopEventHandlerIntent';
  },
   handle(handlerInput) {

      // イベントハンドラーの開始時に指定したトークンが
      // 保存されているセッションアトリビュートを
      // 取得します。
     const sessionAttributes = handlerInput.attributesManager.getSessionAttributes();

     // 応答の作成を開始します。
     const responseBuilder = handlerInput.responseBuilder;
     responseBuilder.withShouldEndSession(false)
                    .speak("ゲームオーバーです。");

     // セッションアトリビュートからイベントハンドラートークンを
     // 取得できたら、それでイベントハンドラーを停止します。トークンが
     // 既に動作を停止しているイベントハンドラーからのものであっても、
     // このディレクティブは何の影響も及ぼさないので、
     // 問題ありません。
     if (sessionAttributes.currentInputHandlerId) {
           // ディレクティブを追加します。
           responseBuilder.addDirective({
               'type': 'CustomInterfaceController.StopEventHandler',
               'token': sessionAttributes.currentEventHandlerToken
             });
     }

      // 応答を作成します。
      const response = responseBuilder.getResponse();

      // CloudWatchへの応答を記述します。
      console.log('===応答=== '+ JSON.stringify(response));

      // 応答を返します。
      return response;

   }
};

クリップボードにコピーされました。

このサンプルコードはAlexa Skills Kit SDK for Javaを使用しています。

@Override
public Optional<Response> handle(HandlerInput input) {

    String speechText = "ではまた。";

    // トークンIDが一致したらイベントハンドラーを停止します。 
    Map<String, Object> attributes = input.getAttributesManager().getSessionAttributes();
    String cachedToken = (String) attributes.getOrDefault(TOKEN_KEY, "MissingToken");

    StopEventHandlerDirective directive =
            StopEventHandlerDirective.builder().withToken(cachedToken).build();

    return input.getResponseBuilder()
            .withSpeech(speechText)
            .withSimpleCard(StatusGaugeHandler.SKILL_TITLE, speechText)
            .addDirective(directive).build();
}

クリップボードにコピーされました。

このサンプルコードはAlexa Skills Kit SDK for Pythonを使用しています。

@sb.request_handler(can_handle_func=lambda handler_input:
                    is_intent_name("AMAZON.CancelIntent")(handler_input) or
                    is_intent_name("AMAZON.StopIntent")(handler_input))
def stop_and_cancel_intent_handler(handler_input):
    logger.info("stop_and_cancel_intent_handler:リクエストを処理します")
    session_attr = handler_input.attributes_manager.session_attributes
    response_builder = handler_input.response_builder

    if EVENT_HANDLER_TOKEN_KEY in session_attr and session_attr[EVENT_HANDLER_TOKEN_KEY] is not None:
        token = session_attr[EVENT_HANDLER_TOKEN_KEY]
        response_builder.add_directive(StopEventHandlerDirective(token))
    return response_builder.speak("ではまた。").response

StopEventHandlerディレクティブには、次のフィールドが含まれています。

フィールド 説明 必須

type

ディレクティブの種類です。CustomInterfaceController.StopEventHandlerに設定する必要があります。

文字列

token

停止するイベントハンドラーに関連付けられている一意のIDです。このトークンは、CustomInterfaceController.StartEventHandlerを使用してイベントハンドラーを開始した際に指定したトークンと一致する必要があります。

文字列

カスタムイベントのタイプ

StartEventHandlerを使用してイベントハンドラーを開始した後、スキルは2つのタイプのリクエストを受信します。

スキルがイベントを受信する順番は、イベントの生成順ではない場合があります。イベントの順番を把握するため、シーケンスIDを生成して、イベントペイロードに含めることができます。スキル側でシーケンスIDを調べることで、イベントが正しい順番で受信されたかどうかを確認できます。しかし、セッションアトリビュートも使用する場合は特に、検出以外でスキルが順不同のイベントを処理する可能性はほとんどありません。

EventsReceivedリクエスト

スキルは、StartEventHandlerディレクティブで指定したフィルター条件をイベントが満たしている場合、このタイプのリクエストを受信します。以下は、EventsReceivedリクエストの例です。例の後に、各フィールドについて説明します。

クリップボードにコピーされました。

{
  "version": "1.0",
  "session": {
     "application": {},
     "user": {}
    }, 
    "request": {
        "type": "CustomInterfaceController.EventsReceived",
        "requestId":"amzn1.echo-api.request.406fbc75-8bf8-4077-a73d-519f53d172a4",
        "timestamp":"2018-12-02T01:29:40.027Z",
        "token": "1234abcd-40bb-11e9-9527-6b98b093d166",
        "events": [
            {
               "header" : {
                   "namespace":"Custom.Robot",
                   "name":"EyeBlink"
               },
               "endpoint": {
                   "endpointId": "amzn1.ask.endpoint.ABCD"
               },
               "payload":{
                   "ack": "ok"
               }    
            }
        ]
    }
}

クリップボードにコピーされました。

このサンプルコードは、Alexa Skills Kit SDK for Node.js(v2)を使用しています。

    canHandle(handlerInput) {
      let { request } = handlerInput.requestEnvelope;
      console.log("CustomEventHandler:処理できるかどうかを確認します" + request.type);
      return (request.type !== 'CustomInterfaceController.EventsReceived');
    },
    handle(handlerInput) {
      // ロボットからのステータス確認応答を処理します。
      console.log("== カスタムイベントの受信 ==");

      let { request } = handlerInput.requestEnvelope;
      let payload = request.events[0].payload;

      if (payload.ack === "ok") {
        return response.speak("ロボットのステータスが正常に更新されました")
          .getResponse();
      }
      return response;
    }

クリップボードにコピーされました。

このサンプルコードはAlexa Skills Kit SDK for Javaを使用しています。

@Override
public Optional<Response> handle(HandlerInput input) {

    EventsReceivedRequest event = (EventsReceivedRequest) input.getRequestEnvelope().getRequest();

    Map<String, Object> attributes = input.getAttributesManager().getSessionAttributes();
    String cachedEndpointId = (String) attributes.getOrDefault(ENDPOINT_KEY, "MissingEndpoint");
    String cachedToken = (String) attributes.getOrDefault(TOKEN_KEY, "MissingToken");

    if (!cachedToken.equals(event.getToken())) {
        // イベントは無視します
        System.out.println("==SessionAttributes==" + JsonUtil.asString(attributes));
        System.out.println("トークンを持つイベントを無視しています: " + event.getToken());
        return Optional.empty();
    }

    // イベントが1つのみと仮定した場合
    Event customEvent = event.getEvents().get(0);
    Header header = customEvent.getHeader();
    String endpointId = customEvent.getEndpoint().getEndpointId();
    String speechText = "予期しないイベントを受信しました。;

    // 名前空間は開始イベントハンドラーのフィルターで既にチェックされています
    if (EVENT_STATUS_ACK.equals(header.getName())) {

        // イベントが同じガジェットからのものであるか検証します
        if (cachedEndpointId.equals(endpointId)) {
            JsonNode payload = JsonUtil.fromMap(customEvent.getPayload());
            if ("ok".equals(payload.get("ack").asText())) {
                speechText = "ロボットのステータスが正常に更新されました";
            }
        }
    }

    return input.getResponseBuilder().withSpeech(speechText, PlayBehavior.ENQUEUE).build();
}

クリップボードにコピーされました。

このサンプルコードはAlexa Skills Kit SDK for Pythonを使用しています。

@sb.request_handler(can_handle_func=is_request_type("CustomInterfaceController.EventsReceived"))
def gadget_event_handler(handler_input: HandlerInput):
    """メッセージがガジェットによって正常に受信されたことを確認します。

    :param handler_input: すべてのリクエストハンドラーに提供されたデータ
    :return: メッセージが正常に受信されたかどうかを示す応答
    """
    logger.info("gadget_event_handler:ガジェットからのイベントを処理します")

    custom_event = handler_input.request_envelope.request  # type: EventsReceivedRequest
    session_attr = handler_input.attributes_manager.session_attributes
    response_builder = handler_input.response_builder

    # イベントトークンを検証します
    cached_token = session_attr[EVENT_HANDLER_TOKEN_KEY]
    event_token = custom_event.token
    if cached_token != event_token:
        logger.info("イベントトークンが一致しません。このイベントを無視します")
        return response_builder.response

    # イベント名を検証します(名前空間はイベントハンドラーのフィルターで既にチェックされています)
    event = custom_event.events[0]
    if EVENT_NAME == event.header.name:
        payload = event.payload

        # ロボットがイベントで送信したステータスメッセージを確認します
        if payload['ack'] == 'ok':
            return response_builder\
                .speak("ロボットのステータスが正常に更新されました", PlayBehavior.ENQUEUE)\
                .response

    return response_builder\
        .speak("このイベントを処理できません", PlayBehavior.ENQUEUE)\
        .response

EventsReceivedリクエストは、次のフィールドを含みます。

フィールド 説明

type

イベントタイプ(CustomInterfaceController.EventsReceived)です。

文字列

requestId

Alexaからスキルに送られたリクエストのIDです。

文字列

timestamp

Alexaからスキルにリクエストが送られた時刻です。

文字列

token

このイベントをディスパッチしたイベントハンドラーのStartEventHandlerによって指定された一意のトークンです。

文字列

events

フィルター条件に一致するイベントのリストです。

配列

events.header

イベントのヘッダーを含むオブジェクトです。

オブジェクト

events.header.namespace

ガジェットに定義されたイベントの名前空間(インターフェース名)です。

文字列

events.header.name

ガジェットに定義されたイベントの名前です。

文字列

events.endpoint

イベントを送ったガジェットのエンドポイントIDを含むオブジェクトです。

オブジェクト

events.endpoint.endpointId

イベントを送ったガジェットのエンドポイントIDです。

文字列

events.payload

この名前空間とタイプのイベントにある、ガジェット定義フィールドを含む自由形式のJSONオブジェクトです。

オブジェクト

Expiredリクエスト

イベントハンドラーが期限切れになると、スキルはこのタイプのリクエストを受信します。以下は、Expiredリクエストの例です。例の後に、各フィールドについて説明します。

クリップボードにコピーされました。

{  
   "version": "1.0",
   "session": {
      "application": {},
      "user": {}
   }, 
   "request": {
      "type": "CustomInterfaceController.Expired",
      "requestId":"amzn1.echo-api.request.406fbc75-8bf8-4077-a73d-519f53d172a4",
      "timestamp":"2018-12-02T01:29:40.027Z",
      "expirationPayload": {
            <JSON Object>
      },
      "token": "1234abcd-40bb-11e9-9527-6b98b093d166"
    }
}

クリップボードにコピーされました。

このサンプルコードは、Alexa Skills Kit SDK for Node.js(v2)を使用しています。

    canHandle(handlerInput) {
      let { request } = handlerInput.requestEnvelope;
      console.log("CustomEventHandler:処理できるかどうかを確認します" + request.type);
      return request.type === 'CustomInterfaceController.Expired';
    },
    handle(handlerInput) {
      console.log("== カスタムイベントの有効期限の入力 ==");

      let { request } = handlerInput.requestEnvelope;
      let data = request.expirationPayload.data;
      let response = handlerInput.responseBuilder
        .withShouldEndSession(true)
        .speak(data)
        .getResponse();
      response.directives = response.directives || [];
      return response;
    }

クリップボードにコピーされました。

このサンプルコードはAlexa Skills Kit SDK for Javaを使用しています。

public class ExpirationEventHandler implements RequestHandler {

    @Override
    public boolean canHandle(HandlerInput input) {
        return input.matches(requestType(ExpiredRequest.class));
    }

    @Override
    public Optional<Response> handle(HandlerInput input) {

        ExpiredRequest event = (ExpiredRequest) input.getRequestEnvelope().getRequest();
        JsonNode payload = JsonUtil.fromMap(event.getExpirationPayload());
        String speechText = payload.get("data").toString();

        return input.getResponseBuilder()
                .withSpeech(speechText, PlayBehavior.ENQUEUE)
                .withShouldEndSession(true)
                .build();
    }
}

クリップボードにコピーされました。

このサンプルコードはAlexa Skills Kit SDK for Pythonを使用しています。

@sb.request_handler(can_handle_func=is_request_type("CustomInterfaceController.Expired"))
def event_handler_expiration_handler(handler_input):
    """イベントハンドラーを停止し、スキルセッションを終了します。

    :param handler_input: すべてのリクエストハンドラーに提供されたデータ
    :return: スキルが終了したかどうかを示す応答
    """
    logger.info("event_handler_expiration_handler:期限切れイベントを処理します")

    request = handler_input.request_envelope.request  # type: ExpiredRequest
    response_builder = handler_input.response_builder
    if request.expiration_payload is None:
        speech_text = "期限切れペイロードがありません"
    else:
        logger.info("期限切れペイロード:" + str(request.expiration_payload))
        speech_text = request.expiration_payload['data']

    return response_builder.set_should_end_session(True).speak(speech_text).response

Expiredリクエストは、次のフィールドを含みます。

フィールド 説明

type

イベントのタイプ(CustomInterfaceController.Expired)です。

文字列

requestId

AlexaからのリクエストのIDです。

文字列

timestamp

Alexaからのリクエストの時刻です。

文字列

expirationPayload

イベントハンドラーが期限切れになった場合にのみスキルが受信する自由形式のJSONオブジェクトです。これは、expiration.expirationPayloadフィールドのStartEventHandlerによって指定されます。

オブジェクト

token

このイベントをディスパッチしたイベントハンドラーのStartEventHandlerによって指定された一意のトークンです。

文字列

イベントの処理

このセクションでは、スキルで受信するイベントの検証方法を例を挙げて説明します。

エンドポイントIDを検証する

カスタムディレクティブに応答してガジェットからスキルにカスタムイベントが送信される場合、スキルはガジェットから送信されるイベントを検証する必要があります。そのためには、エンドポイントIDを検証します。

まず、Endpoint Enumeration APIを使用して、接続済みガジェット(エンドポイントID)のリストを次のとおり保存する必要があります。

クリップボードにコピーされました。

このサンプルコードは、Alexa Skills Kit SDK for Node.js(v2)を使用しています。

let { context } = handlerInput.requestEnvelope;
let { apiEndpoint, apiAccessToken } = context.System;
let response;
try {
  console.log("エンドポイントを確認します");
  response = await Endpoint.getConnectedEndpoints(apiEndpoint, apiAccessToken);
  console.log("v1/endpoints応答:" + JSON.stringify(response));

  if ((response.endpoints || []).length === 0) {
    console.log('利用可能な接続済みエンドポイントがありません');
    response = handlerInput.responseBuilder
    .speak("エンドポイントが見つかりません。ガジェットを接続してからもう一度お試しください。")
    .getResponse();
    return response;
  }

  // エンドポイントIDを保存しておき、後でカスタムディレクティブと
  // イベントが同じガジェットからのものであるか確認できるようにします。
  let endpointId = response.endpoints[0].endpointId;
  console.log("エンドポイントを受信しました。エンドポイントIDを保存しています:" + endpointId);
  const attributesManager = handlerInput.attributesManager;
  let sessionAttributes = attributesManager.getSessionAttributes();
  sessionAttributes.endpointId = endpointId;
  attributesManager.setSessionAttributes(sessionAttributes);
}

接続済みガジェットのエンドポイントIDを保存したら、カスタムイベントの受信時にエンドポイントIDを検証できます。

クリップボードにコピーされました。

このサンプルコードは、Alexa Skills Kit SDK for Node.js(v2)を使用しています。

const CustomEventHandler = {
   canHandle(handlerInput) {
      return handlerInput.requestEnvelope.request.type === 'CustomInterfaceController.EventsReceived';
   },

   handle(handlerInput) {

      const request = handlerInput.requestEnvelope.request;

      // スキルでイベントを取得しようとしているガジェットの
      // エンドポイントIDが保存されているセッションアトリビュートを取得します。
      const sessionAttributes = handlerInput.attributesManager.getSessionAttributes();

      // エンドポイントIDを検証します。
      // 理解しやすくするため、ここではイベントが1つのみであると仮定しています。
      let endpointOfCurrentRequest = request.events[0].endpoint.endpointId;
      return endpointOfCurrentRequest == sessionAttributes.endpointId;
   }
};

イベントの名前空間と名前を検証する

スキルでイベントを受信したら、処理を進める前に、イベントの名前空間と名前を確認する必要があります。受信したイベントの名前空間と名前は、カスタムインターフェースで定義したイベントの名前空間と名前に対応しています。以下に、この検証の実装方法の例を示します。

クリップボードにコピーされました。

このサンプルコードは、Alexa Skills Kit SDK for Node.js(v2)を使用しています。

const EXPECTED_NAMESPACE = "Custom.Robot";
const EXPECTED_NAME = "Blink";

const CustomEventHandler = {
   canHandle(handlerInput) {
      return handlerInput.requestEnvelope.request.type === 'CustomInterfaceController.EventsReceived';
   },

   handle(handlerInput) {

      const request = handlerInput.requestEnvelope.request;
      var infoToSpeak = '';

      // イベントの名前空間と名前を見つけます。
      // 理解しやすくするため、ここではイベントが1つのみであると仮定しています。
      let namespace = request.events[0].header.namespace;
      let name = request.events[0].header.name;
      console.log('イベントを検証しています。名前空間:' + namespace + '、名前:' + name);

      // 名前空間と名前が想定のものかを検証します。
      if (namespace != EXPECTED_NAMESPACE || name != EXPECTED_NAME) {
         infoToSpeak = 'このイベントを処理できません。';
      }
      else {
         infoToSpeak = '次のイベントを受信しました。名前空間 ' + namespace + '、名前 ' + name;
      }

      return handlerInput.responseBuilder
        .speak(infoToSpeak)
        .withShouldEndSession(false)
        .getResponse();
      }
   }
};

期限切れペイロードを処理する

StartEventHandlerを使用してイベントハンドラーを設定する際、イベントハンドラーの期限が切れている場合(つまり、スキルがExpiredイベントを受信した場合)にAlexaに発話させるプレーンテキストexpiration.expirationPayloadを指定する場合があります。

次の例は、expiration.expirationPayloadで定義したgameOverSpeechフィールドに保存したテキストをAlexaに発話させるための方法を示しています。

クリップボードにコピーされました。

このサンプルコードは、Alexa Skills Kit SDK for Node.js(v2)を使用しています。

const EXPECTED_NAMESPACE = "Custom.Robot";
const EXPECTED_NAME = "Blink";

const ExpiredEventHandler = {
   canHandle(handlerInput) {
      return handlerInput.requestEnvelope.request.type === 'CustomInterfaceController.Expired';
   },

   handle(handlerInput) {

      const request = handlerInput.requestEnvelope.request;
      let response = handlerInput.responseBuilder
        .speak(request.expirationPayload.gameOverSpeech)
        .getResponse();
      response.directives = response.directives || [];
      return response;

   }
};

カスタムイベントとセッションアトリビュート

ガジェットが複数のイベントをすばやく送信すると、スキルが同時に呼び出されることがあります。スキルセッション中のデータの保存にセッションアトリビュートを使用している場合、セッションアトリビュートはアトミックに更新されるとは限らないことに注意してください。セッションアトリビュートが同時に更新されることで、新たに追加されたアトリビュートが、ほかの同時リクエストからの応答によってオーバーライドされる可能性があります。例:

スキルに連続的に送信されたイベント:

--> Request for Event1 has sessionAttributes = {}
<-- Response to Event1 has sessionAttributes = {'sawevent1': true}
--> Request for Event2 has sessionAttributes = {'sawevent1': true}
<-- Response to Event2 has sessionAttributes = {'sawevent1': true, 'sawevent2': true}
--> Request for Event3 has sessionAttributes = {'sawevent1': true, 'sawevent2': true}

スキルに同時に送信されたイベント:

--> Request for Event1 has sessionAttributes = {}
--> Request for Event2 has sessionAttributes = {}
<-- Response to Event1 has sessionAttributes = {'sawevent1': true}
<-- Response to Event2 has sessionAttributes = {'sawevent2': true}
--> Request for Event3 has sessionAttributes = {'sawevent2': true}

ガジェットからスキルに複数のイベントが同時に送信されることが予想される場合は、セッションアトリビュートではなく、DynamoDBのようなデータベースを使用してスキルの状態を保存することをお勧めします。