DRS - Lambda関数の作成と管理
AWS Lambda関数は、新規デバイスのディスカバリーと、デバイスからの消費量をスマートホームクラウドに通知する役割を担います。
概要
Lambda関数を用いて、Alexaクラウドが必要とする下記の2種類の情報を送信します:
- ディスカバリーへのレスポンス 接続しようとするデバイスへDRS機能を追加します。Proactive (能動的) あるいは Reactive (受動的) にレスポンスを送信します。
 - 消耗品の在庫量のアップデート デバイスの接続後、消耗品の在庫量を送信します。
 
もし既にLambda関数でスマートホームAPIに対応している場合、既存のDiscover.Responseを拡張し、同じ関数に機能を追加することも可能です。
次のステップに進む前に、デバイスセンサーに関する説明 (Alexa.InventorySensors) (こちら) をよくお読みください。
Lambda関数の作成
- AWSコンソールにログインします(AWS Lambdaコンソール)。スキルを作成するにあたり、AWSのリージョンを下記のように設定してください:
    
- 米国東部(バージニア北部) us-east-1 英語(US)のスキル
 - 欧州(アイルランド)eu-west-1 英語(UK), フランス語(FR), ドイツ語, イタリア語, スペイン語(ES)のスキル
 - 米国西部(オレゴン)us-west-2 日本語のスキル
 - 詳細についてはこちらを確認してください。
 
 - 関数の作成をクリックします。
 - 関数の作成画面で、一から作成を選択します。
 - 関数名とランタイムを入力します(このガイドではランタイムをNode.JSを用いて行います)。関数の作成をクリックします。
 - トリガーを追加を押し、Alexa Smart Homeを選択します。
 - Alexaコンソールにログインし、一つ前のステップで作成したスマートホームスキルを選択します。左側のペインからSmart Homeタブを選択し、スキルIDをコピーします。
 Alexaコンソールは開いたままにしておいてください。
 - Lambdaのトリガーページに戻り、スキルIDをアプリケーションID欄にペーストし、追加をクリックします。
 
AWSコンソールのLambda関数のページの右上に、ARNが表示されています。
- Alexaコンソールに戻り、スマートホームタブ内のデフォルトのエンドポイントに、Lambda関数のARNをペーストします。
 
もしもリージョンごとにスマートホームスキルを作る場合、上記のAWSリージョンごとに作成してください。
1. デバイスのディスカバリー
Lambda関数は、スマートホームAPIの提供するCapabilityインターフェイスを用いて、コネクテッドデバイスのプロパティ、イベントおよびディレクティブを定義します。Lambda関数で、AlexaからトリガーされるDiscover ディレクティブに応答してDiscover.Responseイベントを送信します。あるいは、ディレクティブによる受動的な送信のみならず、コネクテッドデバイスに追加や変更があった場合には、自発的にAddOrUpdateReportイベントを送信します。
アカウントリンクからのディスカバリー
最初のDiscover.Responseによるデバイスの設定は、ユーザーがデバイスメーカーのスマートホームスキルを有効化したときに行われる、アカウントリンクによるDiscoverディレクティブによりトリガーされます。
次の図は、ユーザーがスマートホームスキルを有効化する際の、Alexaクラウドとデバイスメーカーのクラウド間でのやり取りを表しています。

ロジックの実装
まずはDiscover.Responseへの受動的なアクションについての基本的なロジックを実装します。
ReportStateディレクティブへのレスポンスについても、スマートホームスキルに対応したデバイスに応じて実装する必要があります。GitHubリポジトリのAlexa smart homeにサンプルコードがありますので、参考にしてください。- Lambda関数コード画面において、コードエントリタイプは「コードをインラインで編集」に設定されていることを確認してください。ランタイムとハンドラはデフォルト設定のままにしておいてください。
 - 下記のコードをindex.js内のデフォルトのコードをすべて置き換えるようにペーストしてください("capabilities"配列については、以下のステップで説明するJSONをペーストしてください)。
    
exports.handler = function (request, context) { if (request.directive.header.namespace === 'Alexa.Discovery' && request.directive.header.name === 'Discover') { log("DEBUG:", "Discover request", JSON.stringify(request)); handleDiscovery(request, context); } function handleDiscovery(request, context) { var payload = { "endpoints": [{ "endpointId": "appliance-001", // デバイス固有のシリアル番号 "friendlyName": "プリンター", "description": "プリンター by XYZ株式会社", "manufacturerName": "XYZ株式会社", "displayCategories": [ "OTHER" ], "cookie": {}, "capabilities": [ // capabilityを配列として記載します。 // 複数のcapabilityがある場合は、複数の配列が記載できます。 ] }] }; 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); } }; - 右上の保存をクリックします。
 
上記のコードは、次の2つの動作を定義しています。
Discoverディレクティブの待受Alexa.Discoveryレスポンスの送信
受動的なディスカバリーへのレスポンスの中身は、Alexaクラウドが認識可能な予め決められたヘッダーを含みます:
{
  "event": {
    "header": {
      "namespace": "Alexa.Discovery",
      "name": "Discover.Response",
      "payloadVersion": "3",
      "messageId": "00000000-0000-0000-0000-000000000000"
    },
    "payload": {
      // 下記デバイス依存のpayloadが、配列として記載されます。
      // 複数のpayloadがある場合は、複数の配列が記載できます。
    }
  }
}
上記のpayloadオブジェクトは、Alexaのディスカバリーで認識させたい複数のエンドポイントを配列として記載することができます。payloadは各々のデバイス固有の属性が定義されており、AlexaクラウドがAlexa Appに適切なデバイス表示を可能にするために使われます。一つのDiscoveryレスポンスは、例えば2台のプリンターがある場合等、複数のエンドポイントを含むことができます:
  "endpoints": [{
      "endpointId": "appliance-001", // デバイス固有のシリアル番号
      "friendlyName": "プリンター",
      "description": "プリンター by XYZ株式会社",
      "manufacturerName": "XYZ株式会社",
      "displayCategories": [
        "OTHER"
      ],
      "cookie": {},
      "capabilities": [
        // 下記capabilityが配列として記載されます。
        // 複数のcapabilityがある場合は、複数の配列が記載できます。
      ]
    }]
最後に、1つ以上のcapabilityオブジェクトを持つcapability配列を定義します。各々のcapabilityは消耗品再発注に用いるセンサーについて定義します:
{
  "type": "AlexaInterface",
  "interface": "Alexa.InventoryLevelSensor", // デフォルトのセンサータイプ
  "version": "3",
  "instance": "InventoryLevelSensor-1", // 特定のデバイスによるセンサー固有の名前。ユーザー毎に変更しないこと。
  "properties": {
    "supported": [{
      "name": "level" // センサータイプのプロパティ
    }],
    "proactivelyReported": true,
    "retrievable": true
  },
  "capabilityResources": {
    "friendlyNames": [{
      "@type": "text",
      "value": {
        "text": "Magenta Ink", // 英語でのスロット名
        "locale": "en-US"
      }
    },
    {
      "@type": "text",
      "value": {
        "text": "マゼンタインク", // 日本語でのスロット名
        "locale": "ja-JP"
      }
    }]
  },
  // Capability設定
  "configuration": {
    "replenishment": {
      "@type": "DashReplenishmentId",
      "value": "{DRS_ARN}" // スロット毎にアサインされたreplenishmentId
    },
    "measurement": {
      "@type": "Volume", // 消耗品計測のタイプ。Volume=容量
      "unit": "LITER" // 計測の単位。
    }
  }
},
DiscoveryへのResponseのデータ全体はこちらで表示できます:
既存のユーザーに新しいデバイスが追加された場合のディスカバリー
いくつかのシナリオにおいては、デバイスメーカーのクラウドからAlexaクラウドに対し、能動的にデバイス変更の通知を行う必要があります:
- 既に接続されているデバイスに、新しいcapabilityを追加する場合
 - 既にスキルを有効化しアカウントリンクも設定されているユーザーに、新しいデバイスが追加された場合
 
能動的なDiscoveryで送信するJSONには、2つの変更点があります:
- ヘッダーにおける名前を
Discover.ResponseからAddOrUpdateReportに変更 - 送信するpayloadにはscopeを含み、その中で対象ユーザーに紐付いたtokenを指定
 
例:
"endpoint": {
    "scope": {
      "type": "BearerToken",
      "token": "access-token-from-Amazon"
    },
    "endpointId": "appliance-001" // 変更・追加しようとするデバイス固有のシリアル番号
  },
  "payload": {
    //ChangeReport payload
  }
2. 消費量のアップデート
コネクテッドデバイスが消耗品を消費したとき、デバイスメーカーのクラウドはAlexaクラウドに対して通知を行います。Alexaクラウドでは、通知された情報に基づいて消耗品の量をトラックし、在庫量を正確にアップデートします。センサータイプに応じて、2種類の通知方法があります:
- Levelセンサー では、
ChangeReportを能動的(proactively)に送信します。 - Usage および LevelUsage センサーでは、
InventoryConsumedイベントを利用します。 
Levelセンサー
Alexaクラウドへの能動的な消耗品の状態変更を通知するには、ChangeReportを送信します。
このリクエストに対するJSONは下記のようなヘッダーを必要とします:
{
  "context": {},
  "event": {
    "header": {
      "namespace": "Alexa",
      "name": "ChangeReport",
      "payloadVersion": "3",
      "messageId": "00000000-0000-0000-0000-000000000000"
    },
    // Endpointの詳細
  }
}
このエンドポイントには、ユーザーのアカウントに対するアクセストークンを含みます:
"endpoint": {
    "scope": {
      "type": "BearerToken",
      "token": "access-token-from-Amazon"
    },
    "endpointId": "appliance-001" // デバイス固有のシリアル番号
  },
  "payload": {
    //ChangeReport payload
  }
最後に、payloadにはアップデートしたい情報を記載します。この例では容量の絶対値が指定されています:
"change": {
    "cause": {
      "type": "PERIODIC_POLL"
    },
    // ChangeReportにはcapabilityのプロパティに発生した変更情報を通知します。
    // どのタイプのcapabilityで、どのプロパティが変更されたかを記載します。
    "properties": [
      {
        "namespace": "Alexa.InventoryLevelSensor",
        "instance": "InventoryLevelSensor-1",
        "name": "level",
        "value": {
          "@type": "Volume",
          "value": 2.5,
          "unit": "LITER"
        },
        "timeOfSample": "2018-02-03T16:20:50.52Z",
        "uncertaintyInMilliseconds": 0
      },
      {
        // もし他にもプロパティの変更があれば、追加します。
      }
    ]
  }
ChangeReportのデータ全体はこちらで表示できます:
Usageセンサー
InventoryUsageSensorでは、InventoryConsumedイベントを使い、下記の情報を必ず含めます:
- namespace, name および endpointIdを含むデバイスの詳細
 - アクセストークン
 - 在庫量のアップデート情報を含むpayload
 
InventoryConsumedイベントの例:
{  
  "event": {
    "header": {
      "namespace": "Alexa.InventoryUsageSensor",
      "name": "InventoryConsumed",
      "instance": "Sensor.CoffeePod",
      "messageId": "<message id>",
      "payloadVersion": "3"
    },
    "endpoint": {
      "scope": {
        "type": "BearerToken",
        "token": "<Amazonからのトークン>"
      },
      "endpointId": "<endpoint id>"
    },
    "payload": {
      "usage": {
        "@type": "Count",
        "value": 1
      },
      "timeOfSample": "2020-09-23T16:20:50Z"
    }
  }
}
LevelUsageセンサー
InventoryLevelUsageSensorでは、InventoryConsumedおよびInventoryReplacedイベントを用いて使用量情報を報告します。
InventoryConsumed イベントの例:
{  
  "event": {
    "header": {
      "namespace": "Alexa.InventoryLevelUsageSensor",
      "name": "InventoryConsumed",
      "instance": "Sensor.DustFilter",
      "messageId": "<a unique identifier, preferably a version 4 UUID>",
      "payloadVersion": "3"
    },
    "endpoint": {
      "scope": {
        "type": "BearerToken",
        "token": "<an OAuth2 bearer token>"
      },
      "endpointId": "<endpoint id>"
    },
    "payload": {
      "usage": {
        "@type": "Duration",
        "value": "PT30H"
    },
    "timeOfSample": "2020-07-02T16:20:50.52Z"
    }
  }
}
InventoryReplaced イベントの例:
{  
  "event": {
    "header": {
      "namespace": "Alexa.InventoryLevelUsageSensor",
      "name": "InventoryReplaced",
      "instance": "Sensor.DustFilter",
      "messageId": "<a unique identifier, preferably a version 4 UUID>",
      "payloadVersion": "3"
    },
    "endpoint": {
      "scope": {
        "type": "BearerToken",
        "token": "<an OAuth2 bearer token>"
      },
      "endpointId": "<endpoint id>"
    },
    "payload": {
      "replacedDate": "2020-01-15T14:30Z"
    }
  }
}  
Measurementタイプと単位
Discover.Responseでのセンサーの定義で、Measurementタイプと単位は下記のように指定します。
{
  "@type": "Percentage"
}
または
{
  "@type": "Volume",
  "unit": "LITER"
}
デバイスの在庫量・消費量は:
{
  "@type": "Percentage",
  "value": 80
}
または
{
  "@type": "Volume",
  "value": 3.2,
  "unit": "LITER"
}
下記の表は、指定可能なタイプの一覧です。
| @type | unit | 
|---|---|
| Percentage | [No Unit] | 
| Count | [No Unit] | 
| Volume | LITER, MILLILITER, TEASPOON, UK_GALLON, US_FLUID_GALLON, US_FLUID_OUNCE etc. (参照) | 
| Weight | GRAM, KILOGRAM, OUNCE, POUND etc. (参照) | 
参照
- Alexa.Discoveryインターフェース
 - Alexa.InventoryLevelSensor Interface
 - Alexa.InventoryUsageSensor Interface
 - Alexa.InventoryLevelUsageSensor Interface
 - 機能のプロパティのスキーマ
 
次のステップ
ここまでのステップで、スキルの設定が完了しました。次のステップでテストに進みます。
Last updated: Feb 14, 2022

