Alexaに電気とエアコンを操作してもらった(日本語で)

# はじめに
本稿はalexa smart home skills と raspberry piを使った赤外線リモコン操作のお話です
すでに[Smart Home Skillsを使ってみる#qiita](https://qiita.com/sparkgene/items/055d7864c92a80b0c040)という素晴らしい記事がありましてほぼ2番煎じとなります
差分は下記4点となります
- 英語(turn on light) → 日本語(電気をつけて)
- echosim.io → echo 実機動作
- smart home skills ペイロード v2 → v3
- リモコン操作(実デバイス)実装

# できたもの
## 動作
![alexa_smarthome_ir.gif](https://qiita-image-store.s3.amazonaws.com/0/242567/dde2a79d-aead-4351-0560-ec5b27ecdd0d.gif)
ちっちゃい&音無しでごめんなさい
字幕通りに会話してます（信じて）

## 構成
![alexa_demo_smarthome.png](https://qiita-image-store.s3.amazonaws.com/0/242567/32daf20e-8bec-2883-cd06-43687954a8cd.png)

echo dot
↓
alexa smart home skills
↓
lambda 
↓
--- リモコン操作デバイス実装 ---
↓
heroku // MQTTブローカー
↓
Raspberry Pi + 赤外LED
↓
電気/エアコン

# 製作過程
## amazon developer 日本アカウント作る
第1の躓きポイント : developer アカウントを作る罠ボタン
[失敗しないAlexa開発者アカウントの作り方#classmethod](https://dev.classmethod.jp/voice-assistant/how-to-safety-create-alexa-developers-account/)を参考に
普段買い物に使うアカウントでデベロッパーコンソールにログインするだけ
決してアカウント作成してはいけない

何に躓いたかと言うと echo が日本上陸する前に echosim.io で遊ぶため amazon.com のデベロッパーアカウント作ってました
そのままの流れで日本向けスキルを開発していたのですが 
alexa smart home skills から lambda が呼び出されず途方にくれていました
alexa smart home skills からの lambda の呼び出しには["言語"と"リージョン"の制約](https://developer.amazon.com/ja/docs/smarthome/develop-smart-home-skills-in-multiple-languages.html#deploy)があることを知り
試行錯誤の末、amazon.co.jp のデベロッパーアカウントで skill 作るとうまくいきました
あと天気とかも日本の現在地となってくれました

## AWS アカウントを作る
lambdaの実装が必要になるので Amazon Web Service に登録します
アカウントの作り方は参考記事がいっぱいあると思うので割愛します

## heroku アカウントを作る
MQTTブローカーを作るために heroku に登録します
アカウントの作り方h(ry
[爆速でMQTTのパブリックBroker環境を作る方法](https://iot-plus.net/make/raspi/mqtt-broker-on-public-cloud/#toc_id_3_2)を見て heroku 使うことを決めました
\# cloudmqtt単体でも使えるみたいですね

## alexa smart home skills を作る
ここでやっと本題の smart home skills です
前述の[Smart Home Skillsを使ってみる#qiita](https://qiita.com/sparkgene/items/055d7864c92a80b0c040)を参考にしてもらうとできるとできると思います（丸投げ）
ちょっと違うのは日本アカウントで作っているのでページが日本語になってます
\# あっ、最近skill開発のUIが変わってるみたいですね
\# switch to old console で古い方にしてもらうと前述の記事と同じ見た目になるはずです

skill が出来たら alexaアプリのスキル一覧に自分の作成したskillがでると思います
アカウントリンクまで出来たら skill 部分は完成です
(デバイス検索時にlambdaで返す情報でskillの振る舞いが決まるため、skillの実装に必要なのはOAuthくらいです)
ただし、lambda を正しく実装しないと"デバイスが見つかりません"となります

### トラブルシューティング
Q. アカウントリンクがうまくいかない（リンクが変)
A. 認証 URLを確認してください
   ?redirect_url=が正しいかを確認してください
   クライアントID、アクセストークンURL、クライアントシークレットを確認してください
   （私はfirefox使っててクライアントシークレットのコピペになぜかゴミが入っていてうまくいきませんでした)
Q. デバイスが見つからない上に lambda が呼ばれた様子がない
A. 前述の["言語"と"リージョン"の制約](https://developer.amazon.com/ja/docs/smarthome/develop-smart-home-skills-in-multiple-languages.html#deploy)と思われます
   US-west(オレゴン)で lambda を作ってください
   あと、初めの頃、alexaアプリでのデバイス検索が上手くいかなくて、音声で「デバイスを探して」を試したらlambdaが呼ばれた覚えがあります。(曖昧な情報でごめんなさい)
   
## lambda を実装する
第2の躓きポイント : ペイロードバージョン
海外サービスあるあるの、突然のI/F変更
v2→v3で結構変わっています
どっかのサイトからv3で動くサンプル拾ってきました

基本となるのは、デバイス検索要求[Alexa.Discovery]に対する応答と
その応答で登録したデバイスの種類ごとのリクエスト[今回はAlexa.PowerController]に対する応答です

```javascript:index.js
exports.handler = function(request, context) {
  if (request.directive.header.namespace === 'Alexa.Discovery'
      && request.directive.header.name === 'Discover') {
	// "デバイスを検索"が実行されると呼ばれる
    log("DEGUG:", "Discover request", JSON.stringify(request));
    handleDiscovery(request, context, "");
  }
  else if (request.directive.header.namespace === 'Alexa.PowerController') {
	// handleDiscovery() でPowerControllerという種類のデバイスで登録するので
	// そのイベントハンドラ @see: https://developer.amazon.com/ja/docs/device-apis/alexa-powercontroller.html
    if (request.directive.header.name === 'TurnOn'
		|| request.directive.header.name === 'TurnOff') {
      log("DEBUG:", "TurnOn or TurnOff Request", JSON.stringify(request));
      handlePowerControl(request, context);
    }
  }
}
```
コード全文はgithubにあげています
[こちらに](https://github.com/fujimotok/alexa_smarthome_ir)

### Alexa.Discoveryに対する応答
`{ event: { header: {}, payload: {} } }`
という形で、`header` には要求でもらったjsonのheaderをそのまま流用し
`name`を`Discover.Response`に変えたら応答の形になります

`payload`にはこのskillが管理するデバイス（複数可）を`endpoints`に配列で入れて返します

```javascript:endpoint.json
{
  "endpointId": "light（lambdaで処理する際に使う名前)",
  "manufacturerName": "Smart Device Company（会社名？通信するときにxxxに接続中とかで使われている)",
  "friendlyName": "電気（ユーザーが呼び出しに使う名前）",
  "description": "smart switch for light（alexaアプリ上で見える説明文）",
  "displayCategories": ["SWITCH（alexaアプリ上でのデバイスアイコンの種類）"],
  "cookie": {}, // 用途が分かってないので使ってません
  "capabilities": [
    {
      "interface": "Alexa（必須のようです）",
      "type": "AlexaInterface",
      "version": "3"
    },
    {
      "interface": "Alexa.PowerController（スイッチon/offのIF）",
      "version": "3",
      "type": "AlexaInterface",
      "properties": {
        "supported": [{ "name": "powerState" }],
        "retrievable": true
      }
    }
  ]
}
```
詳しくは[公式ドキュメント](https://developer.amazon.com/ja/docs/device-apis/alexa-discovery.html#events-and-properties)

### Alexa.PowerControllerに対する応答
MQTTでデバイスにメッセージ送る処理とAlexa skill kit に応答を返す処理の2つを実装します
MQTTでメッセージ送る際に接続待ちが発生するため`promise`を使って送信完了まで待ってから応答を返します

```javascript:handlePowerControl.js
function handlePowerControl(request, context) {
  // get device ID passed in during discovery
  var requestMethod = request.directive.header.name;
  // get user token pass in request
  var requestToken = request.directive.endpoint.scope.token;
  // get endpointId
  var endpointId = request.directive.endpoint.endpointId;
  var powerResult;

  if (requestMethod === "TurnOn") {
    // Make the call to your device cloud for control 
    powerResult = "ON";
    }
  else if (requestMethod === "TurnOff") {
    // Make the call to your device cloud for control and check for success 
    powerResult = "OFF";
  }

  var mqtt = require('mqtt');
  var mqttpromise = new Promise( function(resolve,reject){
    var options = {
	port: xxxxx,
	clientId: 'mqttjs_' + Math.random().toString(16).substr(2, 8),
	username: "username",
	password: "password",
    };
    var client  = mqtt.connect('mqtt://XXX.cloudmqtt.com', options);
    
    client.on('connect', function() { // When connected
	// publish a message to any mqtt topic
	client.publish(endpointId, powerResult);
	client.end();
	resolve('Done Sending');
    });
    
  });
  mqttpromise.then(
    function(data) {
      console.log('Function called succesfully:', data);
      var response = {
        "context": {
          "properties": [{
            "namespace": "Alexa.PowerController",
            "name": "powerState",
            "value": powerResult,
            "timeOfSample": "2017-02-03T16:20:50.52Z",
            "uncertaintyInMilliseconds": 500
          }]
        },
        "event": {
          "header": {
            "namespace": "Alexa",
            "name": "Response",
            "payloadVersion": "3",
            "messageId": "something",
            "correlationToken": "something"
          },
          "payload": {}
        }
      };
      
      log("DEBUG", "Alexa.PowerController ", JSON.stringify(response));
      return context.succeed(response);
    },
    function(err) {
      console.log('An error occurred:', err);
    }
  );	
}
```
応答のjsonの中身について
詳しくは[公式ドキュメント](https://developer.amazon.com/ja/docs/device-apis/alexa-powercontroller.html#response)

## 赤外線リモコン操作デバイスを作る
### リモコン実装
Raspberry Pi + 赤外LED を LIRC で動かしています
インストール手順などは割愛させていただきます
というかだいぶ前に作ったものの流用のため覚えていない;
[Raspberry Pi Zero で赤外線リモコンを作る#qiita](https://qiita.com/ww24/items/32d2a78c20e6dcc2e68c)などを参考にすれば作れると思います

### lambda との接続
デバイスをクラウドから操作するために今回はMQTTを使いました
理由としては RasPi を port 開けて運用するのが面倒だからです
MQTTを使うとブローカーを介して双方向通信が可能で NAT 越えの手段としてはとてもお手軽です

MQTTの受け取りはPythonで実装しています

```python:sub.py
#!/usr/bin/python
# -*- coding: utf-8 -*-

import paho.mqtt.client as mqtt
import subprocess

host = 'xxx.cloudmqtt.com'
port = 00000

keepalive = 60

def on_connect(client, userdata, flags, rc):
    print('Connected with result code ' + str(rc))
    client.subscribe('light')  # topic名: light を受け取るよう設定
    client.subscribe('aircon') # topic名: aircon を受け取るよう設定


def on_message(client, userdata, msg): # 上で設定した topic が publish されると、ここで受け取る
    print('on_message:' + msg.topic + ',' + str(msg.payload))
    if msg.payload == 'ON':
        cmd = 'on'
    if msg.payload == 'OFF':
        cmd = 'off'

    # 外部コマンドの呼び出しを使って LIRC の送信コマンド irsend を呼んでいます
    # $ irsend SEND_ONCE 機器名 送信コード名
    # この例では topic名が機器名と同じになっています
    retcode = subprocess.call(['irsend', 'SEND_ONCE', msg.topic, cmd])
    print('retcode:'+retcode)

    
if __name__ == '__main__':
    client = mqtt.Client()
    client.username_pw_set('username','password')
    client.on_connect = on_connect
    client.on_message = on_message

	client.connect(host, port, keepalive)
    client.loop_forever()
```

あとは[pythonをデーモン化するメモ#qiita](https://qiita.com/croquisdukke/items/9c5d8933496ba6729c78)を参考にMQTT受け取りをデーモン化して赤外線リモコン操作デバイスは完成です

MQTT通信の疎通確認はcloudmqttのweb上からメッセージ発行できるのでそれで確認できるかと思います

# 課題
デバイスから状態を返していないためalexaアプリ上（音声でも確認できるのかな？）では実際の状態を反映できません
lambdaの中には状態もてないのでデバイスに聞きに行く処理等の実装が必要となります

# おわりに
書き出してみるとごちゃごちゃと長い割に
大事なとこは丸投げみたくなってしまいました
日頃から記録取っておく習慣を持たなければいけませんね
ここまで読んでくださってありがとうございました
質問・指摘大歓迎です