Alexa モーションAPI（Smart Motion extension）を公開中のスキルに追加した

# はじめに

Alexaの首振りデバイスである、Echo Show 10 が日本でも発売されたので、プライマリーユーザーを追尾（首振り）するモーションAPIを試してみた。

# モーションAPIとは

Echo Show 10などのスクリーンが自動的に回転する、モーション対応のデバイスを制御するためのAPIで、`APL Extensions` で制御する。`Extensions`は、APLランタイムに追加のデータソース、コマンド、イベントハンドラーを提供する拡張機能で、APL 1.4 以上で使用可能となっている。

スキルマニフェストとAPLドキュメントの双方に設定が必要で、APLドキュメントで設定された情報が優先される。

<img width="40%" border="1" alt="Echo Show 10" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/426739/29b4ef27-4273-1ff1-1284-45bc5ac56065.png">

# 公開中のスキルに追加してみた

[中学一年の英単語（Alexaスキル）](https://alexa-skills.amazon.co.jp/apis/custom/skills/amzn1.ask.skill.3af5caf0-3599-4da9-8983-84b76a211d82/launch) に `APL Smart Motion extension` を仕込んで再申請したところ、無事に審査をパスした。ひとまず `turnToWakeWord` と `PlayNamedChoreo` で首を振るだけの簡単な追加。審査が通っていることが重要で、今後はAPLドキュメントの修正のみでアップデートが可能なのでいろいろ追加していきたい。

:point_down_tone3: こんな感じで動く（クリック） :point_down_tone3:

[![turnToWakeWordの動作確認](http://img.youtube.com/vi/TYVclCIqh5o/0.jpg)](http://www.youtube.com/watch?v=TYVclCIqh5o)

# どんな事ができるのか

- ウェイクワードが発話された時の<b>ビルトインモーション</b>を指定する。
- <b>コマンド</b>を実行してデバイスを制御する。
- あらかじめ用意されている一定のモーションを再生する。（<b>コレオ</b>）
- デバイスの現状を含むプロパティ値を取得する。（<b>ライブデータプロパティ</b>）
- <b>イベントハンドラー</b>を使用して、デバイスの状態の変化を検知する。

# Smart Motion extension を有効にするには

skill.json の `interfaces` に追加する。
スキル内で使用している全てのAPLドキュメントに適用させる場合は `autoInitializedExtensions` プロパティに追加する。

```
        "interfaces": [
          {
            "type": "ALEXA_PRESENTATION_APL"
          },
          {
            "type": "ALEXA_EXTENSION",
            "requestedExtensions": [
              {
                "uri": "alexaext:smartmotion:10"
              }
            ],
            "autoInitializedExtensions": [
              {
                "uri": "alexaext:smartmotion:10",
                "settings": {
                  "wakeWordResponse": "followOnWakeWord"
                }
              }
            ]
          }
        ]
```

Alexa Developer Console で設定する場合は、メニューから `ビルド` `スロットタイプ` `インターフェース` と辿り `Alexa Extensions` に、ウェイクワードレスポンスの選択と、使用するエクステンションのチェックをする。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/426739/22c0daf0-15cf-c4b4-5fd5-4bfe0092bf4d.png)

# APLドキュメントの記述

`Smart Motion extension` はAPLで制御するため、スキルにAPLを追加する必要がある。

### extensions

まず、`extensions` で `Smart Motion extension` を使用できるようにする。
以下の例では `SmartMotion` という名前（任意）を割り当てたので、以降の操作は `SmartMotion` に対して行うことになる。

```
{
  "type": "APL",
  "version": "1.5",
  "theme": "dark",
  "extensions": [
    {
      "name": "SmartMotion",
      "uri": "alexaext:smartmotion:10"
    }
  ],
```

### settings

`settings` には、`deviceStateName` と `wakeWordResponse` が設定できる。設定方法は以下。

> settings.AssignedName

`AssignedName` は、extensionsで `Smart Motion extension` に割り当てた名前。ここでは`SmartMotion`がそれ。この `SmartMotion` にプロパティ`deviceStateName` を設定することで `DeviceState` 情報を取得できるようになる。そして、スキルマニフェストとは異なる動作にする場合は、`wakeWordResponse` を設定することでウェイクワードを発話した時の<b>ビルトインモーション</b>を変更できる。

```
  "settings": {
    "SmartMotion": {　                         　←---　AssignedName
      "deviceStateName": "MyDeviceState",　　　　←---  データバインディングコンテキスト（※1）
      "wakeWordResponse": "followOnWakeWord"　 　←---　ビルトインモーションの設定
    }
  },
```

※1 `Smart Motion extension` では、データバインディングコンテキストにライブデータオブジェクト `DeviceState` が追加される。 `DeviceState` は、現在のデバイスの物理的な状態とデバイスの機能を表すオブジェクトで、`DeviceState` に含まれるプロパティが<b>ライブデータプロパティ</b>である。

<b>deviceStateName</b> に名前（任意）を設定することで、`DeviceState`にアクセス可能となる。この設定そのものが無かったっり、設定値が""の場合は、ライブデータプロパティにはアクセスできないので注意。ライブデータプロパティは、APLドキュメントのライフサイクル中に変更できるデータオブジェクト。

<b>DeviceStateプロパティ</b>

|No.|プロパティ名|型|動作|
|:--|:--|:--|:--|
|1.|error|文字|読み取り可能なエラーメッセージ。エラーがない場合は""。|
|2.|errorCode|数値|エラーコード。エラーがない場合は0。|
|3.|motionLimit|オブジェクト|モーションの範囲。|
|4.|poise|オブジェクト|モーションと位置の状態。|
|5.|screenAngle|数値|表示方向（度単位）。|

> <b>APL Extensionのライブデータとは</b>
> APL Extensionsは、ドキュメントのグローバルデータバインディングコンテキストに、カスタムの「ライブ」データオブジェクトを追加できます。ドキュメント読み込み時のextensionの静的な状態のみを報告するenvironment.extension環境プロパティと異なり、これらの「ライブ」データオブジェクトは、ドキュメントのライフサイクルを通じ、extensionのユースケースに応じて変化します。

> <b>データバインディングコンテキストとは</b>
> データバインディングを目的としてAPLドキュメントで使用できるデータソースとデータのコレクションです。データはキーと値のペアで構成され、APLドキュメント、デバイスのviewportに関する情報（viewport.widthなど）、デバイスの機能、環境情報（environment.aplVersionなど）に付随するデータソースを含みます。


<b>wakeWordResponse</b> は スキルマニュフェストとAPLドキュメントの両方に設定することが可能だが、両方設定されている場合はAPLドキュメントの設定が優先される。

|No.|設定値|動作|
|:--|:--|:--|
|1.|turnToWakeWord|話者の方向に向きを変え、動きを止める。|
|2.|followOnWakeWord|移動を続けながら同じユーザーを追尾する。（※2）|
|3.|doNotMoveOnWakeWord|デバイスは動かない。|

※2 コマンドでモーションが中断または変更されるまで、ユーザーの追尾を続ける。



### イベントハンドラー

<b>イベントハンドラー</b>には `OnDeviceStateChanged` が設定できる。 `DeviceState` に変化があった場合に実行される。

> AssignedName:EventHandlerName

`AssignedName` には、extensionsで設定した名前 `SmartMotion` を設定し、EventHandlerNameにはハンドラーの名前`OnDeviceStateChanged` を設定し `SmartMotion:OnDeviceStateChanged` とする。イベントが発生した時に `SetValue` 等で値を変更する。その際 `componentId` で対象のコンポーネントと関連づけ、`property` で値を紐づける。

|No.|ハンドラ名|動作|
|:--|:--|:--|
|1.|OnDeviceStateChanged|デバイスの状態が変化したときに呼び出されます。<b>poise</b>を除く|


```イベントハンドラー
  "SmartMotion:OnDeviceStateChanged": [　　　←---　AssignedName:EventHandlerName
    {
      "type": "SetValue",
      "componentId": "mainContainerId",　　　←---　コンポーネントID
      "property": "FaultStatus",     　　    ←---　プロパティ
      "value": "${event.changed.errorCode != 0 ? 'CHANGED' : 'NORMAL'}"
    }
  ],
  "mainTemplate": {
    "items": [
      {
        "type": "Container",
        "id": "mainContainerId",　         　←---　コンポーネントID
        "bind": [
          {
            "name": "FaultStatus",           ←---　プロパティ
            "value": ""
          }
        ],
      }
```

イベントオブジェクトの構造はこんな感じ。

```:マニュアルより転載
{
  "event": {
    "changed": {
        //変更されたすべての状態プロパティ
    },
    "current": {
        //現在のすべての状態プロパティ
    },
    "source": {
      "type": "Document",
      "handler": "OnDeviceStateChanged",
      "id": null,
      "uid": null,
      "value": null
    }
  }
}
```

イベントオブジェクトの使い方。

```
  "when": "${event.changed.errorCode != 0}"
```


# コマンド

`Smart Motion extension`では、新しいextension<b>コマンド</b>が使用できる。`Smart Motionコマンド` と言うらしい。このコマンドでデバイスに対して動作の命令をする。コマンドを実行するためには、コマンドのtypeプロパティを以下のように設定する。

> AssignedName:CommandName

`AssignedName` には、extensionsで設定した名前 `SmartMotion` を設定し、CommandNameには、以下のコマンド名を設定する。`GoToCenter` コマンドを実行する場合は、`SmartMotion:GoToCenter` となる。

|No.|コマンド名|動作|
|:--|:--|:--|
|1.|FollowPrimaryUser|エンゲージメントが最も高いユーザーがいる場合は、そのユーザーに従うようデバイスに指示します。|
|2.|GoToCenter|デバイスを中央の位置に移動します。中央の位置は、デバイスごとに異なります。|
|3.|SetWakeWordResponse|ビルトインウェイクワード応答モーションの動作を変更します。|
|4.|StopMotion|現在のモーションをキャンセルします。|
|5.|TurnToPrimaryUser|エンゲージメントが最も高いユーザーがいる場合は、そのユーザーの方向に回転するようデバイスに指示します。|
|6.|PlayNamedChoreo|指定されたコレオを実行します。|

使い方はこんな感じ。

```
        "item": {
          "type": "AlexaButton",
          "alignSelf": "center",
          "buttonText": "Let's Play Chase!",
          "primaryAction": [
            {
              "type": "SmartMotion:FollowPrimaryUser"
            }
          ]
        }
```

### PlayNamedChoreo

`choreo` <b>コレオ</b>とは、舞踏（dance）という意味があるらしい。
現在は以下の４つが設定できる。

|No.|コレオ名|動作|
|:--|:--|:--|
|1.|ScreenImpactCenter|デバイスをすばやく左右に揺らすモーション。|
|2.|ClockwiseMediumSweep|時計回りに流れる滑らかなモーション。|
|3.|CounterClockwiseSlowSweep|反時計回りに流れるゆっくりとした滑らかなモーション。|
|4.|MixedExpressiveShakes|デバイスの現在の向きをすばやく連続で左右に揺らす。|

コレオを再生するためのAPLへの設定方法はこんな感じ。例えば `onMount` に設定する。

```
  "onMount": [
    {
      "type": "SmartMotion:PlayNamedChoreo",
      "name": "ScreenImpactCenter",
      "delay": 2000
    }
  ],
```

# Smart Motion extensionの仕組み

![仕組み](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/426739/45f0fb87-08b6-61a5-a0de-a91f6e02f3ad.png)

# 再審査

特に指摘もなく、あっさりパスした。

# おわりに

まだ動作が不安定なのかな？と感じる部分もあるように思う。`turnToWakeWord` 設定しているのに、止まらなかったりする時があるが、これから修正されていくのだろう。これらとスキルを組合せることで何ができるのか・・・それを考える方が難しそうだ。

# 参考

- [APL Smart Motion extension（公式）](https://developer.amazon.com/ja-JP/docs/alexa/alexa-presentation-language/apl-ext-smart-motion.html)
- [Alexa Presentation Language（APL）の概要](https://developer.amazon.com/ja-JP/docs/alexa/alexa-presentation-language/understand-apl.html)
- [Extensionのライブデータ](https://developer.amazon.com/ja-JP/docs/alexa/alexa-presentation-language/apl-extensions.html#extension-live-data)
- [中学一年の英単語（Alexaスキル）](https://alexa-skills.amazon.co.jp/apis/custom/skills/amzn1.ask.skill.3af5caf0-3599-4da9-8983-84b76a211d82/launch)