LoginSignup
16
13

Alexa センサー用のスマートホームスキルを作成し、定型アクションのトリガーとして利用する

Last updated at Posted at 2020-04-05

はじめに

Alexaスキルを音声以外の方法で起動させたかったのが発端。

2019年10月末、ようやく定型アクションでスキルが起動できるようになった。さらにEcho Flex(※今は取扱い停止。) も発売され、モーションセンサー(別売)を使うことで定型アクションのトリガーとして使えるようになった。であればスマートホームスキルのセンサー用のスキルを作れば定型アクションのトリガーとして使えるのではないか?と考えセンサー用のスマートホームスキルを作ろうと思ったのがキッカケ。

第四世代のEcho Dot も人感センサーに似た、在室感知という機能が追加されていて、こちらも定型アクションのトリガーとして使用可能となっている。反応はセンサーの方が良いように思う。

全体図

前提

  • カスタムスキルを作ったことがある
  • Alexaスキルモデル:スマートホーム(センサー用)
  • OAuthプロバイダ:Login with Amazon(LWA)

スキル作成の流れ

  1. スマートホームスキルを作成
  2. LWAでセキュリティプロファイルを作成
  3. Lambda関数を作成
  4. Lambda関数のテスト
  5. スマートホームスキルの詳細設定
  6. スマートホームスキルのテスト

スマートホームスキルを作成

Alexa Developer Consoleで、センサー用のスマートホームスキルを新規作成する。

1. Alexa Developer Consoleにログインする

2. スキルを作成する

スキルの作成ボタンをクリックし、必要な情報を入力後スキル作成ボタンをクリックする。
センサー用スマートホームスキルは、デフォルトの言語は英語(米国)を選択する。

No. 項目
1 スキル名 任意(制限事項はマニュアル参照)
2 デフォルトの言語 英語(米国)
3 スキルに追加するモデルを選択 スマートホーム

新しいスキルを作成

※種類が豊富!

3. スキルの情報を入力する

ペイロードのバージョンv3(推奨)を選択したら、いったん保存ボタンをクリックしてスキルを保存しておく。
LWAのセキュリティプロファイルやLambda関数を作成した後に、スマートホームスキルのエンドポイントとアカウントリンクを設定する。

Lambda関数作成時にスキルIDが必要になるので、スキルIDをメモ帳などに保管しておく。
※Alexa Developer Console開けば確認できますが・・・

No. 項目
1 ペイロードのバージョン v3(推奨)

ビルド

LWAでセキュリティプロファイルを作成

アカウントリンクに必要なクライアントIDとクライアントシークレットを作成する。

1. Amazon開発者ポータルにログインする

画面のデザインが変わってる。2020年4月時点のAmazon開発者ポータル画面。

Amazon開発者ポータル

2. セキュリティプロファイルを作成する

上部のメニューからLogin with Amazonをクリックしセキュリティプロファイルを新規作成ボタンをクリックする。
必要な情報を入力後、保存ボタンをクリックする。

No. 項目
1 セキュリティプロファイル名 任意
2 セキュリティプロファイルの説明 任意
3 プライバシー規約同意書URL 任意 ※空欄可
4 同意のロゴ画像 任意 ※選択しなくても良い

セキュリティプロファイル管理

3. クライアントIDとクライアントシークレットの確認

対象セキュリティプロファイルのクライアントIDおよびクライアントシークレットを表示リンクをクリックすると、クライアントIDおよびクライアントシークレットが表示されるので、これをメモ帳などに保管しておく。
※後で確認することでもできる。
※IDが表示されているが、このセキュリティプロファイルは削除済み。

クライアントIDおよびクライアントシークレットを表示

Lambda関数を作成

Lambda関数を新規作成する。

1. AWSコンソールにログインする

2. Lambdaを選択する

スマートホームスキルはLambdaのリージョンが指定されているので注意。
センサー用のスマートホームスキルは"スキルの言語に英語(米国)を設定する"と記載されているが、Lambdaのリージョンは米国西部(オレゴン)を選択しないと動作しない。最初、スキルの言語が英語(米国)なので、Lambdaは米国東部(バージニア北部)で作成したけど動かなかった。

No. スキルの言語 Lambdaリージョン
1 英語(米国)または英語(カナダ) 米国東部(バージニア北部)
2 英語(英国)、英語(インド)、ドイツ語、フランス語(フランス) EU(アイルランド)
3 日本語および英語(オーストラリア) 米国西部(オレゴン)

3. Lambda関数を作成する

関数の作成ボタンをクリックし必要な情報を選択および入力した後、画面右下の関数の作成ボタンをクリックする。
ロールに関しては、マニュアルにはいろいろ書いてあるけど、今回はサンプルなので"基本的なLambda アクセス権限で新しいロールを作成"で作成することにした。詳細はマニュアル参照。

No. 項目
1 オプション 一から作成
2 名前 Lambda関数の名前を入力します
3 ランタイム Node.js 18.x
4 ロール 基本的な Lambda アクセス権限で新しいロールを作成

関数の作成

4. トリガーを追加する

設定タブの+トリガーを追加ボタンをクリックし、トリガーの設定の一覧の中からAlexa Smart Homeを選択する。

トリガーを追加

5. トリガーを設定する

トリガーの設定セクションで、アプリケーションID欄に、先ほど保管したスキルIDを追加する。
トリガーの有効化はオンのままにしておく。
追加ボタンをクリックした後、保存ボタンをクリックする。

トリガーの設定

6. コードを作成する

マニュアル記載のサンプルをセンサー用に修正したもの
exports.handler = function (request, context) {
    if (request.directive.header.namespace === 'Alexa.Discovery' && request.directive.header.name === 'Discover') {
        log("DEBUG:", "Alexa.Discovery:",  JSON.stringify(request));
        handleDiscovery(request, context, "");
    }
    else if (request.directive.header.namespace === 'Alexa') {
        log("DEBUG:", "Alexa:", JSON.stringify(request));
        if (request.directive.header.name === 'ReportState' ) {
            log("DEBUG:", "Alexa:", JSON.stringify(request));
            handleMotionSensor(request, context);
        }
    }
    else if (request.directive.header.namespace === 'Alexa.MotionSensor') {
        log("DEBUG:", "Alexa.MotionSensor:", JSON.stringify(request));
        if (request.directive.header.name === 'detectionState' ) {
            log("DEBUG:", "Alexa.MotionSensor:", JSON.stringify(request));
            handleMotionSensor(request, context);
        }
    }
    else if (request.directive.header.namespace === 'Alexa.EndpointHealth') {
        log("DEBUG:", "Alexa.EndpointHealth:", JSON.stringify(request));
        handleMotionSensor(request, context);
    } 
    else if (request.directive.header.name === 'AcceptGrant') {
        log("DEBUG:", "AcceptGrant:", JSON.stringify(request));    // AcceptGrantの内容をlogに出力
        var response = {
            "event": {
              "header": {
                "namespace": "Alexa.Authorization",
                "name": "AcceptGrant.Response",
                "messageId": request.directive.header.messageId,
                "payloadVersion": "3"
              },
              "payload": {}
           }
        };
        context.succeed(response);
    } 
    else {
      log("DEBUG:", "Other request:", JSON.stringify(request));
      log("DEBUG:", "Other context:", JSON.stringify(context));
    }
    
    function handleDiscovery(request, context) {
        var payload = {
            "endpoints":
            [
              {
                "endpointId": "sensor-001",
                "manufacturerName": "TAKARA MOTION SENSOR",
                "friendlyName": "TAKARAのセンサー",
                "description": "TAKARAのセンサー(スマートホームスキル)",
                "displayCategories": ["MOTION_SENSOR"],
                "cookie": {
                  "key1": "このエンドポイントのキーと値のペアです",
                  "key2": "複数のエントリーがある場合があります",
                  "key3": "参照目的で使用します",
                  "key4": "現在のエンドポイントの状態を維持するためには使わないでください"
                },
                "capabilities": [
                  {
                    "type": "AlexaInterface",
                    "interface": "Alexa",
                    "version": "3"
                  },
                  {
                    "type": "AlexaInterface",
                    "interface": "Alexa.MotionSensor",
                    "version": "3",
                    "properties": {
                      "supported": [
                        {
                          "name": "detectionState"
                        }
                      ],
                      "proactivelyReported": true,
                      "retrievable": true
                    }
                  },
                  {
                    "type": "AlexaInterface",
                    "interface": "Alexa.EndpointHealth",
                    "version": "3",
                    "properties": {
                      "supported": [
                        {
                          "name": "connectivity"
                        }
                      ],
                      "proactivelyReported": true,
                      "retrievable": true
                    }
                  }
                ]
              }
            ]
        };
        var header = request.directive.header;
        header.name = "Discover.Response";
        log("DEBUG", "Discovery Response:", JSON.stringify({ header: header, payload: payload }));
        context.succeed({ event: { header: header, payload: payload } });
    }

    function log(message, message1, message2) {
        console.log(message + message1 + message2);
    }

    function handleMotionSensor(request, context) {
        // 検出中に渡されたデバイスIDを取得します
        log("DEBUG", "handleMotionSensor: ", JSON.stringify({ request }));
        var requestMethod = request.directive.header.name;
        var responseHeader = request.directive.header;
        responseHeader.namespace = "Alexa";
        responseHeader.name = "Response";
        responseHeader.messageId = responseHeader.messageId + "-R";
        // リクエスト中のユーザートークンパスを取得します
        var requestToken = request.directive.endpoint.scope.token;
        var SHResult;

        // デバイス制御クラウドを呼び出して制御します 
        SHResult = "DETECTED";
        var contextResult = {
            "properties": [
                            {
                              "namespace": "Alexa.MotionSensor",
                              "name": "detectionState",
                              "value": SHResult,
                              "timeOfSample": "2017-02-03T16:20:50.52Z",
                              "uncertaintyInMilliseconds": 0
                            }
                          ]
        };
        var response = {
            context: contextResult,
            event: {
                header: responseHeader,
                endpoint: {
                    scope: {
                        type: "BearerToken",
                        token: requestToken
                    },
                    endpointId: "sensor-001"
                },
                payload: {}
            }
        };
        log("DEBUG", "Alexa.MotionSensor ", JSON.stringify(response));
        context.succeed(response);
    }
};

7. LambdaのARNをコピーする

作成したLambda関数のARNをメモ帳などに保管しておく。

Lambda関数のテスト

作成したLambda関数をテストする。

1. テストを作成する

画面右上の方にあるテストボタンをクリックする。

Lambdaのテスト

※現在はテストの位置が変わっていて、テストタブになっています。
image.png

2. テストイベントを作成する

イベントテンプレートは、デフォルトのHello Worldのままにしておく。

image.png

Discoveryディレクティブ
{
    "directive": {
        "header": {
            "namespace": "Alexa.Discovery",
            "name": "Discover",
            "payloadVersion": "3",
            "messageId": "1bd5d003-31b9-476f-ad03-71d471922820"
        },
        "payload": {
            "scope": {
                "type": "BearerToken",
                "token": "access-token-from-skill"
            }
        }
    }
}

イベント名にDiscoveryと入力し、エディターのコンテンツをすべて以下のコードで置き換え変更を保存ボタンをクリックする。

image.png

3. テストを実行する

テストボタンをクリックする。成功すると以下のような結果が表示される。

image.png

詳細をクリックし、以下のような実行結果が表示されれば成功。

image.png

レスポンス例
Response:
{
  "event": {
    "header": {
      "namespace": "Alexa.Discovery",
      "name": "Discover.Response",
      "payloadVersion": "3",
      "messageId": "1bd5d003-31b9-476f-ad03-71d471922820"
    },
    "payload": {
      "endpoints": [
        {
          "endpointId": "sensor-001",
          "manufacturerName": "TAKARA MOTION SENSOR",
          "friendlyName": "TAKARAのセンサー",
          "description": "TAKARAのセンサー(スマートホームスキル)",
          "displayCategories": [
            "MOTION_SENSOR"
          ],
          "cookie": {
            "key1": "このエンドポイントのキーと値のペアです",
            "key2": "複数のエントリーがある場合があります",
            "key3": "参照目的で使用します",
            "key4": "現在のエンドポイントの状態を維持するためには使わないでください"
          },
          "capabilities": [
            {
              "type": "AlexaInterface",
              "interface": "Alexa",
              "version": "3"
            },
            {
              "type": "AlexaInterface",
              "interface": "Alexa.MotionSensor",
              "version": "3",
              "properties": {
                "supported": [
                  {
                    "name": "detectionState"
                  }
                ],
                "proactivelyReported": true,
                "retrievable": true
              }
            },
            {
              "type": "AlexaInterface",
              "interface": "Alexa.EndpointHealth",
              "version": "3",
              "properties": {
                "supported": [
                  {
                    "name": "connectivity"
                  }
                ],
                "proactivelyReported": true,
                "retrievable": true
              }
            }
          ]
        }
      ]
    }
  }
}

スマートホームスキルの詳細設定

スマートホームスキルにエンドポイントと、アカウントリンクを設定する。

1. Alexa Developer Consoleに戻る

2. エンドポイントを設定する

スマートホームサービスのエンドポイントのデフォルトのエンドポイントボックスに、作成したLambda関数のARNを入力する
保存ボタンをクリックする。

エンドポイント

3. アカウントリンクを設定する

左ペインからアカウントリンクをクリックし、必要事項を入力した後保存ボタンをクリックする。

認証画面のURIは、認可リクエストを参照。
アクセストークンのURIは、アクセストークンリクエストを参照。

No. 項目
1 認証画面のURI https://www.amazon.com/ap/oa
2 アクセストークンのURI https://api.amazon.com/auth/o2/token
3 ユーザーのクライアントID ※セキュリティプロファイルのクライアントID
4 ユーザーのシークレット ※セキュリティプロファイルのクライアントシークレット
5 ユーザーの認可スキーム HTTP Basic認証 (推奨)
6 スコープ profile
7 ドメインリスト
8 デフォルトのアクセストークンの有効期限
9 Alexaのリダイレクト先のURL https://pitangui.amazon.com/api/skill/link/MA67IWRRJKXIU
https://layla.amazon.com/api/skill/link/MA67IWRRJKXIU
https://alexa.amazon.co.jp/api/skill/link/MA67IWRRJKXIU

アカウントリンク

スマートホームスキルのテスト

スキルの詳細設定が完了し、Lambda関数のテストが完了したら、スマートホームスキルをテストする。

1. Alexaアプリにログインする

2. 対象のスマートホームスキルを有効にする

有効にするボタンをクリックしてスキルを有効にすると、スキルをデバイス制御クラウドにアカウントリンクするページにリダイレクトされる。

スキル有効化

3. アカウントリンク

EメールアドレスAmazonのパスワードを入力し、ログインボタンをクリックする。
"スマートホームが正常にリンクされました。"と表示されれば成功。

アカウントリンク アカウントリンク成功

4. スマートホームスキルを検出する

Alexaアプリで端末を検出し、作成したスマートホームスキルがデバイスの一覧に表示されれば成功。

すべてのデバイス

5. 動作テスト

Alexaアプリで定型アクションを作成する。
実行条件の設定でスマートホームを選択し、デバイス一覧で作成したスマートホームスキルを選択し、実行条件を検出に設定する。
※センサー用のスマートホームスキルにしないとデバイス一覧には表示されない。

定型アクションの実行条件を設定 定型アクションのデバイスを選択 実行条件は検出にする

外部からイベントゲートウェイにChangeReportを送信して、定型アクションが動作すれば成功。
今回はブラウザからChangeReportを送信した。

デモ

記載している内容は、2020年4月5日時点での情報。変更されている可能性があるので注意。

おわりに

実は、センサーデバイスがなくてもChangeReportディレクティブを送信してあげればスマートホームスキルは動いてくれる。つまり、イベントゲートウェイにChangeReportディレクティブを送信する仕組みは、デバイスでもアプリでもプログラムでもブラウザでも何でも良い。顔認証、距離、温度、湿度や天気など、いろいろな変化をトリガーにしてAlexaを起動することができるようになる。

参考

関連

16
13
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
16
13