Alexa Smart Home Skillを若干爆速に開発する

この記事は スマートスピーカー2 Advent Calendar 2018 3日目 の記事です。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/96675/3b05ce29-19f5-87ad-bfde-2b37bf531603.png)

この記事ではAmazon EchoなどのAlexa搭載スマートスピーカーでスマートデバイスを操作することのできる、**スマートホームスキルを楽ちんに実装する方法**を紹介します。

# TL;DR / 結論から先に言うと
Alehos3というAlexa Smarhome Skill用フレームワークを使用すると、誰が書いてもだいたい同じような冗長な記述を排除して **シンプル・簡潔にAlexa Smart Home Skillを作成することができます。**
Alehos3は以下で公開されています。
https://github.com/nqd/alehos3

※ 現在、開発版ということで、プロダクション環境での利用は非推奨となっています。

# はじめに / Alexa Smart Home Skillとは

Alexaは、**Amazonが設計した会話モデルに則ってロジックを実装する**プリビルドモデルと、**開発者が会話モデルとそのロジックを設計・実装する**カスタムモデルのどちらかを選択してスキル作成することができます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/96675/04a3697b-6ad9-0f8f-38a0-3f576bc994ac.png)


Alexa Smart Home Skillは、前述のプリビルドモデルのひとつで、Amazonが設計した会話モデルに則って、スマートホームデバイスをコントロールするロジックをLambdaに実装することで、スマートホームサービスを提供することができるようになります。

開発する側からすると、会話フローや、Alexaの応答はAmazonが定義してくれるので、楽で便利です。


また、ユーザも「○○(のスキル)を開いて」という発話なしに、デバイスを操作できるため手軽にデバイスを操作できるという利点があります。

# Alexa Smart Home Skillを作成するには

詳細な手順は[公式のドキュメント](https://developer.amazon.com/ja/docs/smarthome/steps-to-build-a-smart-home-skill.html)に任せますが、大雑把にAlexa Smart Home Skillを作成するには、以下を実現する必要があります。

- 開発者ポータルから、Alexa Smart Home Skillを作る
- スマートホームの利用者が誰なのかOAUTH 2.0で区別できるように設定する
- スマートホームスキルからのイベントを処理して、レスポンスするLambdaを作る
- 利用者が操作可能なスマートホームデバイスをLambdaが答えられるように実装する
- 操作可能なスマートホームデバイスの操作要求にLambdaが答えられるように実装する
- Amazonへスキル公開の申請をする

スマートホームスキルを作成して設定を済ませると、作成したLambdaに
**「操作できるデバイスと、そのデバイスでできることを教えて」** というイベントと、
**「このデバイスをこういう風に操作しろ」**というイベントが[インターフェース](https://developer.amazon.com/ja/docs/device-apis/list-of-interfaces.html)に則り**決まった形で**飛んでくるようになります。

そして、それらのイベントに対して**[インターフェース](https://developer.amazon.com/ja/docs/device-apis/list-of-interfaces.html)に則り、決まった形でレスポンスする**必要があります。

# Alexa Smart Home SKillのめんどくさいところ

Alexa Smart Home Skillは、インターフェースに則り、決まった形でのリクエストを行い、決まった形でのレスポンスを要求します。
ですが、現時点(2018/12/02時点)で、Alexa Smart Home Skill向けのSDKは提供されていません。

そのため、リクエストされたJSONを読み込み、何のインターフェースに対するイベントなのかを判定し、処理を分岐させ、デバイスを操作して、レスポンスする仕組みを**スマートホームスキル開発者各々で**実装する必要があります。

また、レスポンスする内容の一部も、リクエストされた内容から作成することが可能ですが、その処理も**スマートホームスキル開発者各々で**実装する必要があります。

エラー発生時においても、指定されたインターフェースに則って決められたフォーマットで、エラーコードをレスポンスする必要がありますが、これも**スマートホームスキル開発者各々で**実装しなければなりません。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/96675/7086b8d4-595d-32aa-1b3f-7e5f5e11e041.png)

誰が書いても変わらないコードを実装したり、お作法的にJSONを記述しなければならず、**めんどくさい**です。
**できれば、誰かに実装してほしいところです。**

願わくば、リクエストされたインターフェースの判定や振り分け・分岐、エラーコードの返却など定型的な処理は**フレームワークに任せ**、デバイスの操作やその操作結果の記述に注力したいところです。

# できるだけ楽をする

そういった、**「誰が書いてもだいたい同じやんけ！」**というスマートホームスキルの処理を、ある程度担ってくれるフレームワークがあります。
**Alehos3**といいます。

Alehos3はGitHubで公開 ( https://github.com/nqd/alehos3 )されていて、誰でも利用することができます。

Alehos3を導入することで、Alexa Smart Home Skillのボイラープレートコードを排除し、注力したいデバイスの操作や、その操作結果の記述に専念することができます。

# Alehos3がサポートするインターフェース

2018/12/03時点でAlehos3がサポートするインターフェースは以下の通りで、主にスマートホームデバイスの電源操作のみが可能です。
(内部的にはエンターテイメントデバイスや、ブライトネスの操作も実装されているようですが)

- Alexa.Discovery
- Alexa.PowerController
- Alexa.Authorization

# Alehos3でスマートホームスキルを実装する

前置きが長くなりましたが、実際にAlehos3を利用してスマートホームスキルを実装してみようと思います。

使い方は簡単で、Alehosクラスをnewしたあとに、各インターフェースのハンドラーを実装していきます。
そして、Lambdaのエントリポイントで受け取った引数をそのまま、alehosのhandleに渡せばOKです。

```js:handler.js
const Alehos = require('alehos3');

const alehos = new Alehos();

// リクエストがAlexa.Discovery IFのイベントなら、ここで登録されたハンドラーが実行される。
alehos.registerHandler('discover', (request, callback) => {
	// TODO: 操作可能なスマートホームデバイスと、そのデバイスができることを示すpayloadを作成する

	callback(null, null, payload); // callback(error, properties, payload);
});

//Alexa.PowerController IFのTurnOnディレクティブを含むリクエストなら、ここで登録されたハンドラーが実行される。
alehos.registerHandler('powerControllerTurnOn', (request, callback) => {
	// TODO: requestからスマートデバイスIDなどを取得して、指定されたスマートデバイスの電源をONにする処理を記述
	
	callback(null); // callback(error, properties, payload);
});


//Alexa.PowerController IFのTurnOffディレクティブを含むリクエストなら、ここで登録されたハンドラーが実行される。
alehos.registerHandler('powerControllerTurnOff', (request, callback) => {
	// TODO: requestからスマートデバイスIDなどを取得して、指定されたスマートデバイスの電源をOFFにする処理を記述
	
	callback(null); // callback(error, properties, payload);
});

// Lambdaが実行されたらalehosでリクエストをハンドリングする
exports.handler = (event, context, callback) => {
  alehos.handle(event, context, callback);
};
```

これで、Alexa.PowerContollerのTurnOn/TurnOffディレクティブをハンドリングする準備ができました。
あとは、TODOに沿ってお手元のスマートデバイスを操作する処理を実装するだけでOKです。

Lambdaのエントリポイントに渡される、eventとcontextは、handlerが渡してくるrequestに以下の形で格納されています。

```js:handler.js
const request = { 
    event: event,
    context: context
};
```

そのため、イベントでリクエストされる内容(操作するデバイスのIDなど)を参照したい場合、は```request.event```配下を参照することで確認できます。


レスポンスのJSONは、**Alehos3がいい感じに作成して、レスポンスしてくれます。**

# PropetiesやPayloadを指定してレスポンスする

スマートデバイスの状態をAlexaにpropertiesとして設定してレスポンスしたり、Discovery IFのようにpayloadを指定してレスポンスしたい場合はHandlerから渡されるcallbackの各引数に指定します。

- 第一引数：エラーレスポンス(正常完了の場合はnull)
- 第二引数：[properties](https://developer.amazon.com/ja/docs/device-apis/alexa-property-schemas.html)
- 第三引数：payload

propertiesやpayloadの形式は、通常AlexaのレスポンスJSONのproperties要素に指定する、場合と同じフォーマットです。

具体的には以下のように指定することで、指定したpropertiesやpayloadをレスポンスに含めてくれるようになります。

```js:handler.js
const Alehos = require('alehos3');

const alehos = new Alehos();

alehos.registerHandler('powerControllerTurnOn', (request, callback) => {
    // ※ 指定されたスマートデバイスの電源をONにしようとしたとする
    const properties = {};  // 実際はここにpropertiesのオブジェクトを指定 
    const payload = {};     // 実際はここにpayloadのオブジェクトを指定
    callback(null, properties, payload); // callback(error, properties, payload);
});
//(以下省略)
```


# エラーをレスポンスする。

エラーをレスポンスする場合には、エラーオブジェクトを作成して、エラーコードとエラーメッセージを定義して、callbackの第一引数に渡します。

具体的には以下のような形です。

```js:handler.js
const Alehos = require('alehos3');

const alehos = new Alehos();

alehos.registerHandler('powerControllerTurnOn', (request, callback) => {
	// ※ 指定されたスマートデバイスの電源をONにしようとしたとして、
	// 　　　　ここでスマートデバイスにアクセスできなかったエラーが発生したとする

	const err = new Error();
	err.payload = {
	  type: alehos.code.ERR_ENDPOINT_UNREACHABLE,
	  message: 'device is unreachable'
	};
	
	callback(err); // callback(error, properties, payload);
});
//(以下省略)
```

エラーコードは、[alehos.code](https://github.com/nqd/alehos3/blob/master/code.js)に、公式の[エラーコード](https://developer.amazon.com/ja/docs/device-apis/alexa-errorresponse.html#error-type-values)に対応する形で定義されています。

パラメータにmessageを指定できるものの、指定したmessageをAlexaがエラー発生時に発話するわけではありません。
Alexaはエラーコードに応じて、Amazonが予め用意した発話を行います。

# さいごに

Alehos3を使用すると、冗長なコードを排除して比較的シンプルにスマートホームスキルを実装することができました。
サービスとしてスマートホームスキルを提供するのは些か大規模になり大変ですが、個人で遊んで見るにはお手軽で、楽しいのではないでしょうか。

冬休みの自由研究的に、スマートホームスキルを実装して、クリスマス用の電飾を操作したりとか、ギミックを動かして見ても楽しそうですね！
それでは、良いスマートホームライフを！