スマートホームスキルの作り方、シリーズ第2回目です。
第1回は次をご確認ください。
「スマートホームスキルを作る (1) サンプルコードからスキルの基本要素を作る」
Alexa からスキルへの各要求はディレクティブという形でスキルのエンドポイントLambda 関数に送られてきます。その主要なものは次の3つに分類でき、いずれも適切に処理される必要があります。
ここでは「スマートホームスキルを作る (1) サンプルコードからスキルの基本要素を作る」で作成した、サンプルコードを使って説明を進めていきます。先にこのサンプルコードが確認できる環境を用意しておいてください。
ユーザーがスマートホームAPIスキルを有効にした後に、最初に行うのはデバイスの検出です。Alexaはこれを通して、特定のスキルが特定のユーザーに対して操作をすることができるデバイスの一覧を得ます。ユーザーがデバイスの検出を求めたら、Alexa はユーザーが有効にしている各スマートホームスキルに Alexa.Discovery インターフェースの Discover という要求をディレクティブとして送ります。
前回作成したサンプルプログラムでは次の箇所で Discover 要求に対する処理をしています
def handle_discovery_v3(request):
endpoints = []
for appliance in SAMPLE_APPLIANCES:
endpoints.append(get_endpoint_from_v2_appliance(appliance))
response = {
"event": {
"header": {
"namespace": "Alexa.Discovery",
"name": "Discover.Response",
"payloadVersion": "3",
"messageId": get_uuid()
},
"payload": {
"endpoints": endpoints
}
}
}
return response
ここで確認してほしいのは endpoints オブジェクトです。これは endpoint オブジェクトをリストとして収めているもので、一つの endpoint オブジェクトは一つのスマートホームデバイスの情報を提供します。提供する情報は、デバイスの名前、デバイスが対応している機能、デバイスの表示カテゴリーなどです。これらの情報を得ることで、Alexaはユーザーごとにデバイスの呼び出し名(デバイス名)と利用できる機能とを識別し、ユーザーの要求に対して適切な処理ができるようにしています。サンプルプログラムでは SAMPLE_APPLIANCES で定義されているデバイスリストの一つ一つを get_endpoint_from_v2_appliance という関数で endpoint オブジェクトの形式に変換しています。get_endpoint_from_v2_appliance 関数は次のような内容です。
def get_endpoint_from_v2_appliance(appliance):
endpoint = {
"endpointId": appliance["applianceId"],
"manufacturerName": appliance["manufacturerName"],
"friendlyName": appliance["friendlyName"],
"description": appliance["friendlyDescription"],
"displayCategories": [],
"cookie": appliance["additionalApplianceDetails"],
"capabilities": []
}
endpoint["displayCategories"] = get_display_categories_from_v2_appliance(appliance)
endpoint["capabilities"] = get_capabilities_from_v2_appliance(appliance)
return endpoint
endpoint オブジェクトの重要な要素を簡単に確認しましょう。
displayCategories と capabilities はそれぞれ get_display_categories_from_v2_appliance、get_capabilities_from_v2_appliance という別の関数で内容を生成しています。これらの関数はそれぞれ SAMPLE_APPLIANCES で定義されている modelName の内容にしたがって典型的な設定が行われるようにしています。前回日本語環境で利用できる modelName を紹介しましたが、これは正確には modelName をもとに生成される displayCategories と capabilities の値でサポートされるものを紹介していました。 displayCategories に設定できる値は次の通りです。displayCategories は配列のため複数設定することもできますが、通常一デバイス一つを設定します。
値 | 内容 | 日本語対応 (2018年3月12日時点) |
---|---|---|
ACTIVITY_TRIGGER | シーン。特定の状態に遷移するために複数のデバイスが一定の順序で操作されるようなものを示します | ○ |
CAMERA | カメラ | × |
DOOR | ドア | ○ |
LIGHT | 照明器具 | ○ |
OTHER | 既定のカテゴリーに該当しないデバイス | ○ |
SCENE_TRIGGER | シーン。特定の状態に遷移するために複数のデバイスが操作されるようなものを示します。ACTIVITY_TRIGGERと異なり、それぞれの操作は常に一定の順序である必要はないものを示す。 | ○ |
SMARTLOCK | スマートロック | ○ |
SMARTPLUG | スマートプラグ | ○ |
SPEAKER | スピーカー | × |
SWITCH | スイッチ | ○ |
TEMPERATURE_SENSOR | 温度センサー | × |
THERMOSTAT | サーモスタット | × |
TV | テレビ | × |
capabilities には capability オブジェクトの配列を与えます。capability オブジェクトではデバイスが対応する機能を示します。capability オブジェクトの詳細は、次の仕様を確認してください。
https://developer.amazon.com/ja/docs/device-apis/alexa-discovery.html#capability-object
デバイスがどのようなユーザーの要求に応えることができるかは capability オブジェクトの interface の値で「機能インターフェース」として示します。現時点で日本語環境で正式に対応できている機能インターフェースは次の表1の通りです。
カテゴリー | 機能 | インターフェース |
---|---|---|
電力制御 | オン/オフを切り替える | Alexa.PowerController |
電力制御 | 電力レベルを設定する | Alexa.PowerLevelController |
照明制御 | 照明の輝度をパーセンテージで変更するか、特定の値に変更する | Alexa.BrightnessController |
照明制御 | 照明の色を変更する | Alexa.ColorController |
照明制御 | 調整可能な照明の白の色調を変更する | Alexa.ColorTemperatureController |
ドアロック制御 | ロック状態を取得または設定する | Alexa.LockController |
パーセンテージ | デバイスをパーセンテージで制御する | Alexa.PercentageController |
(2018年3月12日時点で日本語に対応しているインターフェースだけを載せています。英語環境ではこれ以外にホームエンターテイメントデバイスの操作に関連したインターフェースなど様々なバリエーションがあります。これらは順次日本語にも対応していく予定です)
endpoints オブジェクトを実際のスキルで構築するには、Discover を要求したユーザーを特定し、そのユーザーのデバイス一覧を得る必要があります。サンプルプログラムではユーザーを特定する部分は実装されていません。
ユーザーの特定はアクセストークンを使って行います。アクセストークンの取得方法について次を確認してください。
request["payload"]["scope"]["token"]
以上、Discoveryの処理の重要なポイントを見てきました。詳細は次を確認してください。
https://developer.amazon.com/ja/docs/device-apis/alexa-discovery.html
Alexa からのスマートホームデバイスの操作要求は、ディレクティブとしてスキルのエンドポイントLambda 関数に送られます。デバイスごとに何が操作要求として送られてくるかは、先の Discovery への応答に依存します。逆にいうと Discovery の応答で明示的に対応できることを示した要求以外が特定のデバイスに対して要求されることはありません。例えば、明るさの指定をサポートすることを Discovery への応答で示さなかったデバイスに対して、明るさの指定のディレクティブが送られることはありません。
スキルは要求を受け取ると、メッセージの内容から次を特定し、これに応じて対象デバイスを操作、その処理結果を応答メッセージとして Alexa に返す必要があります。
前回作成した、サンプルプログラムでは次の部分がデバイス操作ディレクティブの処理を行い、結果応答のメッセージを作成しています。
def handle_non_discovery_v3(request):
request_namespace = request["directive"]["header"]["namespace"]
request_name = request["directive"]["header"]["name"]
if request_namespace == "Alexa.PowerController":
if request_name == "TurnOn":
value = "ON"
else:
value = "OFF"
response = {
"context": {
"properties": [
{
"namespace": "Alexa.PowerController",
"name": "powerState",
"value": value,
"timeOfSample": get_utc_timestamp(),
"uncertaintyInMilliseconds": 500
}
]
},
"event": {
"header": {
"namespace": "Alexa",
"name": "Response",
"payloadVersion": "3",
"messageId": get_uuid(),
"correlationToken": request["directive"]["header"]["correlationToken"]
},
"endpoint": {
"scope": {
"type": "BearerToken",
"token": "access-token-from-Amazon"
},
"endpointId": request["directive"]["endpoint"]["endpointId"]
},
"payload": {}
}
}
return response
操作要求の内容はディレクティブの中の namespace と name を確認することでわかります。このコードの前半、以下の部分で、これらを判断しています。
request_namespace = request["directive"]["header"]["namespace"]
request_name = request["directive"]["header"]["name"]
if request_namespace == "Alexa.PowerController":
if request_name == "TurnOn":
value = "ON"
else:
value = "OFF"
namespace は要求内容を判断する上で機能インターフェースに紐付きます。name はその機能インターフェースが提供する機能名の一つです。つまり、ユーザーは「namespace 機能インターフェースの name 機能を実行する」ことを要求していることになります。上記箇所 はAlexa.PowerController 機能インターフェースの TurnOn / TurnOff 操作要求について処理をしています。
次に必要なのはユーザーと操作対象デバイスの特定です。この部分はサンプルコードでは簡略化のために省略されていますので、情報を得るためのヒントをお伝えします。
request["directive"]["endpoint"]["scope"]["token"]
request["directive"]["endpoint"]["endpointId"]
ユーザーと操作対象デバイスが特定できたら、次に該当のデバイスを操作します。これは通常、デバイス操作クラウドのAPIを呼び出すことによって行います。その方法は様々ですし、どの方法が最適というわけでもないので、ここでは具体的な例は割愛しますが、通常守るべき重要なポイントは以下の通りです:
ここで「通常」と書いたのは例外もあるからです。たとえば、スマートロックなどの鍵のロック処理が例外として挙げられます。ロック処理は、時間がかかる場合もあるという配慮から、非同期による遅延応答にも対応しているためです。遅延応答についてはここでは説明しませんが、興味がある方はこちらを確認してください。
(*1) 仕様上の同期応答のタイムリミットは8秒です。しかし8秒以内に応答すれば大丈夫という考え方は適切ではありません。ユーザービリティーその他の条件も考慮し、ディレクティブの処理は全体で長くとも7秒程度に納めるべきです |
デバイスの操作が終わったらその結果を Alexa に応答メッセージとして返します。
サンプルコードでは次の部分で応答用のオブジェクトを作成し、関数の戻り値として返しています。
response = {
"context": {
"properties": [
{
"namespace": "Alexa.PowerController",
"name": "powerState",
"value": value,
"timeOfSample": get_utc_timestamp(),
"uncertaintyInMilliseconds": 500
}
]
},
"event": {
"header": {
"namespace": "Alexa",
"name": "Response",
"payloadVersion": "3",
"messageId": get_uuid(),
"correlationToken": request["directive"]["header"]["correlationToken"]
},
"endpoint": {
"scope": {
"type": "BearerToken",
"token": "access-token-from-Amazon"
},
"endpointId": request["directive"]["endpoint"]["endpointId"]
},
"payload": {}
}
}
return response
これは Alexa.PowerController 機能インターフェースに対応する応答ですが、ほとんどの箇所が他の機能インターフェースの場合も共通です。違いが出てくるのは properties の中身で、ここに何を与えるかは各機能インターフェースの仕様を確認する必要があります。
サンプルプログラムでは、この後、生成された応答メッセージのデータの整合性(不正な文字列が入っていないかなど)を確認したのち、Alexa に応答します。
以上がディレクティブを処理し、応答するまでの一連の流れです。
ここで見てきたのは Alexa.PowerController 機能インターフェースの場合の例でした。明るさの変更など、要求内容によっては特定のパラメータ値を持つものもあります。どのような機能インターフェースがあり、それぞれがどのようなユーザー要求に対応し、どのようにスキルに伝えられ、またどのようにスキルから Alexa に応答すべきかについて詳細は既出の表1を確認してください。
スマートホームスキルAPIによって実現される機能は声によるスマートホームデバイスのコントロールだけではありません。スマートホームデバイスの現在のステータスを Alexa に通知する仕組みがあり、これにより Alexa は現在のデバイスの状態を把握します。デバイスの状態は様々な形で使われます。その代表的なものの一つが Alexaアプリでのデバイスのステータスの表示です。 Alexa アプリはデバイスの状態に応じて表示を切り替えます。
例:Alexa アプリでのデバイスのステータスの確認
スイッチデバイスは ON 状態
スイッチデバイスは OFF 状態
Alexa は最新のデバイスの状態が必要な時、これを次のいずれかの方法で判定します。
ここでは2番目の「デバイス状態レポートをディレクティブとして送りその結果から」に対応するためのスキルの実装方法について説明します。3番目の「変更通知イベントから」につては、非常に重要な項目ですので、次回詳しくご説明いたします。
デバイス状態レポート(ReportState)要求はデバイスの現在の状況をAlexaが必要としている時に Alexa から発行されるディレクティブです。スキルはこれに適切に応答する必要があります。ReportState では該当デバイスが対応している全ての属性情報について最新の状態をレポートする必要があります。
サンプルプログラムにはこの ReportState に対応するコードが含まれていません。先に出てきた Alexa.PowerController 機能インターフェースにのみ対応したデバイスに対しての ReportState への応答を handle_non_discovery_v3 関数の中に追加する場合は、例えば以下のように書くことができます。ここでは常にデバイスはオンライン状態にあり、常に ON の状態であると返しています。
elif request_namespace == "Alexa":
if request_name == "ReportState" :
responses = {
"context": {
"properties": [
{
"namespace": "Alexa.EndpointHealth",
"name": "connectivity",
"value": {
"value": "OK"
},
"timeOfSample": get_utc_timestamp(),
"uncertaintyInMilliseconds": 200
},
{
"name": "powerState",
"namespace": "Alexa.PowerController",
"value": "ON",
"timeOfSample": get_utc_timestamp(),
"uncertaintyInMilliseconds": 200
}
]
},
"event": {
"payload": {},
"header": {
"namespace": "Alexa",
"name": "StateReport",
"payloadVersion": "3",
"messageId": get_uuid(),
"correlationToken": request["directive"]["header"]["correlationToken"]
},
"endpoint": {
"endpointId": request["directive"]["endpoint"]["endpointId"]
}
}
}
return responses
デバイス状態レポート(ReportState)要求について詳細は、次を確認してください。
今回はここまでです。
次回は変更通知イベントとエラー状態の扱い方、スキルを公開するまでの流れについてご説明する予定です。
このブログの続きは、こちらをご覧ください: スマートホームスキルを作る(3) イベント通知機能の実装と、スキルの公開