obnizプラグインをAlexaスマートホームデバイスにする

以前の記事 [ESP32をAlexa Gadgets Toolkitデバイスにしよう](https://qiita.com/poruruba/items/e4559e18cb90cd0a3b81) でAlexa Gadgets Toolkitを絡めてAlexaを操ってみました。
今度は、AlexaのスマートホームをESP32で実現しようと思います。
すでに以前の記事 [スマートホームスキルを作る(2)：いよいよスマートホームスキルを作成する](https://qiita.com/poruruba/items/5e31b82bfbdeef20519d) で作成経験があるので、そこまで苦ではなかったのですが、ちょうどObnizでプラグインという機能が提供されたのでそれを活用してみることが今回のモチベーションです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/4562fc8e-3cbc-f934-d1ef-f31b12e67bf1.png)

スマートホームのセットアップ時に、誰のアカウントのデバイスを操作するのかアカウント認証し、以降はそのアカウントにデバイスを紐づけ、Echoデバイスに対して声で操作できるようにします。
今回は、アカウント認証にGoogleアカウントを使いました。OpenID Connect準拠であれば何でも良いです。例えばCognitoでもOKです。
アカウントに紐づいたどのデバイスを操作するかは、Obniz IDで識別します。
とはいっても、今回は複数のアカウントに対してそれぞれObnizを区別できるようには作っておらず、Obniz IDは固定にしています。（もし余力があれば、GoogleアカウントにObniz IDを紐づけ管理すればそれも可能かと思います。）

(参考) スマートホームスキルAPIについて
　https://developer.amazon.com/ja-JP/docs/alexa/smarthome/understand-the-smart-home-skill-api.html

ソースの説明は今度にしますがとりあえず、GitHubに上げておきます。

poruruba/AlexaHome
　https://github.com/poruruba/AlexaHome

#サポートするデバイス

以下のURLに示す通り、たくさんの機能インタフェースが提供されていますが、今回はこのうち以下の機能を実装します。

・PowerController
・LockController
・PowerLevelController
・TemperatureSensor

(参考)機能インタフェースの一覧
　https://developer.amazon.com/ja-JP/docs/alexa/device-apis/list-of-interfaces.html

#Alexaスキルの作成

まずは、とりあえず、Alexaスキルを作成しましょう。
alexa developer console
　https://developer.amazon.com/alexa/console/ask

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/e83a8f99-6921-04de-4089-2698ae5a1230.png)

「スキルの作成」ボタンを押下します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/f201db84-595a-a5ec-435c-2ba0cad398a7.png)

スキルに追加するモデルとしてスマートホームを選択し、「スキルを作成」ボタンを押下します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/48ec79a5-49e0-fc57-8b24-f05e6cbcd996.png)

左側のスマートホームを選択し、右側に表示されたスキルIDをクリップボードにコピーします。
次にLambdaを作成します。いまだにオレゴンで作成しないと思われるので、右上のリージョンをオレゴンにします。

　https://us-west-2.console.aws.amazon.com/lambda/home?region=us-west-2#/functions

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/79a9e90d-8b87-59e2-eca3-7a23075a5ea9.png)

適当に関数名を決めて作成します。Node.js 12.xで行きます。
ロールを既存のロールから選択する場合は、既存のロールのログ出力先がap-northeast-1に制限されている場合があるので、us-west-2(オレゴン)からも出力できるように変更するのが都合が良いです。そうすれば、console.logがちゃんとCloudWatchに記録されます。

次に、デザイナにおいて、「＋トリガーを作成」ボタンを押下し、トリガとして「Alexa Smart Home」を選択します。アプリケーションIDのところに先ほどコピーしたスキルIDを入力します。最後に「追加」ボタンを押下します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/5ca3016b-955b-230e-26c4-7e74dcca3bc1.png)

次に、右上にある、このLambdaのARNを覚えておきます。

alexa developer consoleに戻って、デフォルトのエンドポイントと、極東のチェックボックスをOnにしたうえで、両方のエンドポイントにさきほどのLambdaのARNを指定しておきます。最後に、「保存」ボタンを押下します。

次に、左側のナビゲーションから、アカウントリンクを選択します。
ここで、一番下の「Alexaのリダイレクト先のURL」に表示されている3つのURLをメモっておきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/35c3a64c-d128-1a5f-86a5-4b4255718709.png)

#Googleアカウントでサインインの準備

GCPのコンソールを開きます。

Google Cloud Platform Console
　https://console.cloud.google.com

左上のメニュボタンから、APIとサービス→認証情報を選択します。
上の方にある「＋認証情報を作成」クリックし、OAuthクライアントIDを作成します。

アプリケーションの種類：ウェブアプリケーション
名前：適当に
承認済みのリダイレクトURI：Alexaのリダイレクト先のURL

承認済みのリダイレクトURIのところに、alexa developer consoleで表示されていたAlexaのリダイレクト先のURLを3つとも入力します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/6711e460-4bcf-bfcd-d0bf-506d98fb1500.png)

これで、GCPからクライアントIDとクライアントシークレットが生成されますので、これをメモっておきます。

もう一度、alexa developer consoleに戻って、以下、作業します。
ユーザーがユーザーアプリケーションやウェブサイトからアカウントをリンクできるようにします のスイッチをOnにします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/35c3a64c-d128-1a5f-86a5-4b4255718709.png)

以下を入力します。

・Web認証画面のURI：https://accounts.google.com/o/oauth2/v2/auth
・アクセストークンのURI：https://oauth2.googleapis.com/token
・ユーザーのクライアントID：GCPから生成されたクライアントID
・ユーザーのシークレット：GCPから生成されたクライアントシークレット
・ユーザの認可スキーム：HTTP Basic認証（推奨）
・スコープ：openid

ユーザーのリダイレクト先のURLは、何か１つ適当なURLを入力しないとエラーとなるようです。
また、AlexaクライアントIDとAlexaクライアントシークレットは後で使うのでメモっておきます。
最後に、「保存」ボタンを押下します。

#ESP32アプリの作成

ソースコード詳細は、GitHubを参照してください。

ちなみに、以下の4つのデバイスを持っていることにしています。``` DEVICE_INFO devices[]```の辺りをみるとわかります。

|endpointId|friendlyName|機能インタフェース|displayCategory|
|---|---|---|---|
|device1|テストのトグル|PowerController|SWITCH|
|device2|テストのサーモ| TemperatureSensor | TEMPERATURE_SENSOR |
|device3|テストのロック| LockController | SMARTLOCK |
|device4|テストのパワーレベル| PowerLevelController | LIGHT |

いろいろ追加してみてください。manufacturerNameは適当な会社名です。
ちなみに、device3のTemperatureSensorは、ESP32のCPUの温度を返すようにしています。device1のPowerControllerは、LED_IO で示すポートのHIGH/LOWを切り替えています。LEDがつながっていれば、付けたり消したりできます。

で、実装上大事なのは、setup()の

```c
  obniz.commandReceive(onCommand);
  obniz.start();
```

の部分と、以下のコールバック関数です。

```c
void onCommand(uint8_t* data, uint16_t length){
```

外部から、ObnizのSDKを使ってESP32に電文を送ると、この関数が呼ばれて受信した電文を処理できます。
Alexaクラウドから、処理要求が飛んでくるのですが、それを受信して、適切な返答を作って返しています。
実際には、Lambdaのスキルコードがいったん受け付けたうえで転送されてくるので、手分けして処理しています。

以下に対応するインテントが転送されてきます。

・Alexa.Discovery.Discover
・Alexa.Authorization.AcceptGrant
・Alexa.PowerController.TurnOn
・Alexa.PowerController.TurnOff
・Alexa.LockController.Lock
・Alexa.LockController.Unlock
・Alexa.PowerLevelController.SetPowerLevel
・Alexa.PowerLevelController.AdjustPowerLevel
・Alexa.ReportState

(参考) スマートホームスキルAPIのメッセージリファレンス
　https://developer.amazon.com/ja-JP/docs/alexa/smarthome/smart-home-skill-api-message-reference.html

今回サポートする機能インタフェースで、最低限必要なインテントを実装しているつもりです。

Obnizのプラグインについては、以下が参考になります。最初はよくわからなかったのですが、手を動かしてみたらすんなり理解できました。

　https://obniz.io/ja/doc/guides/obniz-starter-guide/plugin/io

事前に、ArduinoIDEにプラグインのための環境設定が終わっている前提です。

　https://obniz.io/ja/doc/reference/obnizos-for-esp32/plugin/arduino-ide

上記を設定して、実装したプログラムをESP32に書き込んで、起動すると、UARTで、デバイスキー入力やら、接続するWiFiアクセスポイントの指定やらが聞かれますので、以下を参考に入力します。

　https://obniz.io/ja/doc/reference/obnizos-for-esp32/quick-start/#%E3%82%BF%E3%83%BC%E3%83%9F%E3%83%8A%E3%83%AB%E3%81%8B%E3%82%89%E3%81%AE%E8%A8%AD%E5%AE%9A

問題なければ、以下のように表示されて、Onlinecloudに接続完了した旨が表示されるはずです。

```
obniz ver: 3.4.0
obniz id: XXXXXXXX
WirelessLAN MAC Address: XXXXXXXXXXXX
WiredLAN MAC Address: XXXXXXXXXXXX
Press 's' to setting mode
Input char >>

Wi-Fi Scanning...
Wi-Fi Connecting SSID: XXXXXXX
Connecting Cloud
Onlinecloud Connected
```


#Lambdaの実装

それでは、Lambdaに配置するスキルコードの実装です。
これも、GitHubを参考にしてください。

Obnizの仕様が少々扱いにくくて、トリッキーな実装していますが、ご容赦ください。。。。(なんと危険な実装なのでしょう。。。)

```js:index.js
var g_obniz_success;
var g_obniz_failed;

var Obniz = require("obniz");
var obniz = new Obniz(OBNIZ_ID, { auto_connect: false });
obniz.onconnect = async function () {
    console.log("connected");
    obniz.plugin.onreceive = async (data) =>{
        var str = Buffer.from(data).toString("utf-8");
        var res = JSON.parse(str);
        console.log(res);
        if( g_obniz_success )
            g_obniz_success(JSON.parse(str));
    };
}
obniz.onclose = async function(){
    console.log("obniz onclose");
    if( g_obniz_failed )
        g_obniz_failed("obniz onclose");
}

async function obniz_tranceive(intent, message){
    console.log('obniz connecting');
    await obniz.connectWait();
    console.log('obniz connected');

    return new Promise((resolve, reject) =>{
        if( obniz.connectionState != 'connected' )
            throw 'obniz disconnected';

        g_obniz_success = resolve;
        g_obniz_failed = reject;
        obniz.plugin.send(JSON.stringify({ intent: intent, value: message }));
    })
}
```

obniz.plugin.send のところでesp32のobnizに電文を送り、obniz.plugin.onreceive で応答を受信しています。
こんな感じで使います。intent名とendpointIdなどのパラメータを渡すと、ObnizであるESP32と接続し、ObnizのプラグインでESP32に届けてもらい、レスポンスをリターンしてくれます。

```js
contextResult = await obniz_tranceive(intent, { endpointId: endpointId, level: level });
```

processDirectiveが主要な関数で、単純に、右から左、左から右に流しているものがほとんどですが、intent= Alexa.Discovery.DiscoverやAlexa.Authorization.AcceptGrantは、少しLambdaで処理しています。

ちなみに、Alexa.Authorization.AcceptGrant で取得されるアクセストークンやリフレッシュトークンは、非同期にアレクサクラウドにイベントを送るときに使いますが、今回はやめて次回にでも説明します。

以下は、環境ごとに書き換えが必要です。

・【Obniz ID】：ESP32に書き込んだObnizのObniz ID
・【AlexaクライアントID】：alexa developer consoleで払い出されたAlexaクライアントID
・【Alexaクライアントシークレット】：alexa developer consoleで払い出されたAlexaクライアントシークレット

ソースコードに直接書き換えてもよいですし、のちほど設定するLambdaの環境変数に指定するのでも良いです。

# Lambdaにアップロード

> cd test-home
> npm install

npm install で、以下をインストールしています。

・node-fetch
・obniz

あとは、これらをZIPに固めて、さきほど作ったLambdaにアップロードします。
結構ファイルが大きいので、オンラインエディタで修正できなくなるのがつらいですが。。。

次に、環境変数を指定します。最低限 ```HELPER_BASE = ./helpers/``` と指定します。
OBNIZ_IDやALEXA_CLIENT_ID、ALEXA_CLIENT_SECRETも指定してもよいでしょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/3dedbc25-f868-d2fb-4f01-2d3c055b518b.png)

それから、Obnizとの接続に少し時間がかかる場合があるので、基本設定のタイムアウト時間を少し長めにします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/8b06396c-1135-04f4-cad4-bf19aefcefd1.png)

# Alexaスマートホームスキルの公開設定

一応これで、準備OKなので、先に公開設定しておきます。
alexa developer consoleで公開を選択し、必須の情報を入力します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/0fd428a7-0af9-1442-3fc3-7c32959b385e.png)

適当に入力してください。。。公開名は適当に「テストホーム」とでもしておきました。
小さなスキルアイコン(108x108)と大きなスキルアイコン(512x512)が必要です。
いろんな作り方がありますが、以下もあります。

https://poruruba.github.io/utilities/
→ ユーティリティから画像ファイルを選択、icon sizeにalexaを選択。
　あとは、適当な画像ファイルをアップロードしてファイルに保存ボタンを押下するだけです。

(参考)
　[便利ページ：Javascriptでアイコンファイルを生成する](https://qiita.com/poruruba/items/268eb6b1f58bce3cff51)

公開範囲のページが表示されます。ここで、このスキルにアクセスできるユーザとして公開を選択し、ベータテストのところに、メールアドレスを入力します。
最後に「保存して続行」。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/63f64ef4-9058-bea1-5abe-758e6496ab67.png)

#使い方

さきほどの公開設定で、おそらくメールが飛んでいるかと思いますので、それに従ってテストスキルを登録します。以下も参考にしてください。

　[スマホのAlexaアプリで試してみる](https://qiita.com/poruruba/items/0e13eb7146f4d59ebd4e#%E3%82%B9%E3%83%9E%E3%83%9B%E3%81%AEalexa%E3%82%A2%E3%83%97%E3%83%AA%E3%81%A7%E8%A9%A6%E3%81%97%E3%81%A6%E3%81%BF%E3%82%8B)

メール→ブラウザ→Alexaアプリという順番で起動します。
以降は、Alexaアプリからの作業になります。

それでは、スキルを有効にしましょう。
まずは、アカウント認証を求められます。ちゃんとGoogleアカウント認証が立ち上がってます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/4d5b1961-efc1-3d74-4827-180695c519b7.png)

無事に、リンク完了しました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/9fb25d8e-2f89-bd80-40ba-ef2efdeb206c.png)

ESP32のシリアルコンソールを見ると、何やら通信が発生しているようです。
詳細は次回。

続いて、端末というかデバイスの検索が始まります。ESP32で4つのデバイスを設定していました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/7b8c9c60-34cf-db90-afb9-ef3c0f91b42b.png)

検出中、ちょっとどきどきしますが。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/c1099c47-7cb4-b801-3a6a-b2ab1da6f8aa.png)

めでたく検出されました。数もあっているようです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/4d0f91ff-22a6-9e97-fab8-c196a71cf64b.png)

あとは、適当にセットアップします。グループに入れると分類が楽です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/66d452b2-2f22-b539-b87d-299655bdeb21.png)

完了です！

各デバイスがどんな感じに見えるかというと、

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/e06ad15e-f8d5-5a88-d3ab-50e6327e2177.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/99ee47f6-9f17-c82d-9306-34084425c838.png)
 
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/17093587-b946-ef7e-0bbb-9b6903c1cd43.png)
 
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/49ee6998-3179-0e81-13f8-157467b06418.png)

ってな感じです。（パワーレベルはもっと操作しやすかったような。。。）
ちなみに、テストのロックは、何も設定しないと、ロックのみできてロック解除はアプリからはできません。設定から、Alexaアプリからロック解除するように変更することが可能です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/0db51bf2-58da-4c97-4256-4b76e0c19ed4.png)

あとは、お待ちかねの音声での指示ですが、例えば、「アレクサ、テストのトグルのオンにして」というと、ちゃんとオンにしてくれるはずです。

#終わりに

また記事が長くなってしまったので、ソースコード解説は次回で。

(うーん、なんか不安定。時間がたつと反応しなくなるような気がする。。。おそらくOpenID Coonectのリフレッシュトークンでの更新がうまくいってないのかな。。）

以上