RaspberryPiでスマートホーム 〜AlexaでMQTT〜
MQTT(AWS IoT)使ってラズパイでJSONデータを受信、USB赤外線リモートアドバンスで照明の操作ができるようになりました。
Javascriptを使ってブラウザからMQTTで送信して、ラズパイに指令することもできるようになりました。
今度はAlexaを使って指令してみます。私はAmazon Echo Dotを使います。
AlexaのスキルをAmazon Developer Consoleで、実際の処理(MQTTで送信)の部分をAWS Lambdaで作成していきます。
Alexaスキルの作成
Amazon Developer Consoleでのスキルの作成方法は、以下の記事を参考にしてください。
ここではポイントのみ記載していきます。
モデルを決める
スキルを作るにあたって、呼び出し名、発話サンプルを決めます。
操作したいのは照明です。まずはオン・オフのみを実装したいと思います。
- アレクサ、ラズパイで照明つけて
- アレクサ、ラズパイで照明消して
で、これを実装しようとすると、まず考えるのはスロットを対象機器(”照明”)とコマンド(”つけて”、”消して”)にすることです。つまり発話サンプルはこんな感じです。
”{TARGET} を {COMMAND}”
TARGET部分が”照明”、COMMAND部分が”つけて”か”消して”に差し込まれるわけです。
問題発生
が、しかし…
結論から先に言うと、これだと問題がありました。単語がスマートホームスキルとかぶるようで、自分のスキルではなくそっちが呼ばれてうまくいきませんでした。
あれやこれや苦闘してようやく見つけた解決法はスロットを使わないことでした。
つまり”しょうめいをつけて”、”しょうめいをけして”、とベタに記述してやるとうまくいきました。どうもスロット使うと駄目みたいです。
ちょっとださくなりますが、しょうがないのでベタに行きます。
スキルの作成
Amazon Developer Consoleでモデルを定義します。
スキル名は”AlexaHome”にしました。忘れずに”日本語”にしておきます。
カスタムスキルを選択して次に進みます。
呼び出し名は”ラズパイ”。
次にインテントを作成していきます。”インテントを追加”ボタンを押して作っていきます。
照明をつけるインテント。他にも似たような文章を登録しておくと、それらの文章でも照明がつけられるようになります。
照明を消すインテント。
念のため、全てひらがなで登録しました。それがいいのかはちょっと分かりませんが…
インテントを作成したら保存して、ビルドします。
AWS Lambda関数の作成
AWSでの処理の作成方法は以下の記事を参考にしてください。
ここではポイントのみ記載していきます。
IoTのポリシーの追加
上記の手順で一度作成します。今回は関数名を”alexaHome”、ロール名を”roleAlexaHome”にしてます。
上記の記事と違って今回はAWS IoTを使うので、その権限が必要です。先ほど作成したロールにIoTの権限を追加します。
サービス一覧の検索窓に”IAM”と入れ検索し、IAMをクリックします。
ロールをクリックします。
先ほど作成したロールを選択します。
”ポリシーのアタッチ”ボタンを押します。
検索窓に”iot”と入力して検索、一覧の中から”AWSIoTDataAccess”にチェックを入れて、”ポリシーのアタッチ”ボタンを押します。
Lambdaに戻ると、IoTが追加されています。
ソース
AWSのAlexa用サンプルソースを元にいらない部分を削りました。以下のソースを、AWSに貼り付けます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 |
/* eslint-disable func-names */ /* eslint quote-props: ["error", "consistent"]*/ 'use strict'; const Alexa = require('alexa-sdk'); const Aws = require('aws-sdk'); //========================================================================================================================================= const APP_ID = undefined; const ENDPOINT = "(エンドポイント)"; const SKILL_NAME = 'AlexaHome'; const HELP_MESSAGE = '家電を操作します。対象の機器とコマンドをおっしゃってください。'; const HELP_REPROMPT = '対象の機器とコマンドをおっしゃってください。'; const STOP_MESSAGE = 'さようなら!'; const FAILURE_MESSAGE = '失敗しました!'; var speechWord = { 'light' : 'しょうめい', 'on' : 'つけました', 'off' : 'けしました', }; //========================================================================================================================================= function submit(resource, command, params, callback) { var speechOutput = ""; var data = { topic: 'homeapp/to', payload: JSON.stringify({ "resource": resource, "command": command, "parameters": params, }), qos: 0 }; var iotdata = new Aws.IotData( { endpoint: ENDPOINT } ) iotdata.publish(data, function(err, data){ if(err){ console.log("Failure: ",err); speechOutput = FAILURE_MESSAGE; } else{ console.log("Success"); speechOutput = speechWord[resource] + "を" + speechWord[params]; } if (callback) { callback(speechOutput); } }); } const handlers = { 'LaunchRequest': function () { this.emit('AMAZON.HelpIntent'); }, 'LIGHT_OFF': function() { submit("light", "power", "off", function(speechOutput){ this.response.speak(speechOutput); this.emit(':responseReady'); }.bind(this)); }, 'LIGHT_ON': function() { submit("light", "power", "on", function(speechOutput){ this.response.speak(speechOutput); this.emit(':responseReady'); }.bind(this)); }, 'AMAZON.HelpIntent': function () { const speechOutput = HELP_MESSAGE; const reprompt = HELP_REPROMPT; this.response.speak(speechOutput).listen(reprompt); this.emit(':responseReady'); }, 'AMAZON.CancelIntent': function () { this.response.speak(STOP_MESSAGE); this.emit(':responseReady'); }, 'AMAZON.StopIntent': function () { this.response.speak(STOP_MESSAGE); this.emit(':responseReady'); }, }; exports.handler = function (event, context, callback) { const alexa = Alexa.handler(event, context, callback); alexa.APP_ID = APP_ID; alexa.registerHandlers(handlers); alexa.execute(); }; |
Lambda関数が呼び出されると、まずexports.handler()が呼ばれます。そして次にhandlersの中でインテント名に応じた引数でsubmit()関数を呼び出します。スロットが使えなかったので無駄に長くなってしまいます。
インテント名はAmazon Developer Consoleでインテントを定義したときにつけた名前です。引数はそのままラズパイに送るリソース名、コマンド名、パラメータと送信が完了したときに呼び出されるコールバック関数です。
submit()関数ではAWS Iotに接続して、引数をJSONにしたものを送信(publish)します。publishは非同期なので、処理完了のコールバックの中でAlexaの返事用の文字列を組み立てます。
それぞれのリソース等に対応した日本語を辞書で持っておき、文字列を組み立てます。speechOutputという変数の中身をAlexaがしゃべります。最後にコールバック関数を呼び出します。
ラズパイのエンドポイントを確認
”(エンドポイント)”を実際のエンドポイントに置き換えておきます。エンドポイントはラズパイをAWSに登録したときのものです。
AWSのサービス一覧で”iot”で検索、”IoT Core”選択
IoT Coreの画面で”管理”をクリック。
”モノ”をクリック、登録したラズパイを選択。
”操作”をクリック、表示されたHTTPSがエンドポイントになります。
関数を作成したら、Amazon Developer Consoleのエンドポイントにこの関数のARNを貼り付けましょう。
テスト
Amazon Developper Consoleでテスト
ではやってみます。まずはAmazon Developer Consoleのテスト用ツールを使います。
テキストを貼りつけて、エンターキーで送信です。
うまくいきました。と、結果だけ書くと簡単に見えちゃいますけど、ここにたどり着くまで何度テストしたことやら…
実機でテスト
続いて実機です。
うまくいかないときは…
テストは以下の記事を参考にしてみてください。
チェックポイントとしては…
スキルのモデル定義(Amazon Developer Console)
- ビルドは成功したか
- インテント名は間違ってないか
- 発話サンプルは間違ってないか
- エンドポイントのARNは間違ってないか
発話サンプルは入力した後に、右側の”+”を押し忘れてたことがよくありました。
Alexaシミュレータ(Amazon Developer Console)
- 結果が正常に返ってくるか
まずはここで正常に動かないことには、どうしようもないです。
Lambda関数(AWS)
- ”一から作成”ではなく”設計図”から作成したか(今回のやり方の場合)
- 文法は間違ってないか
- エンドポイントは設定したか
- スキルのインテント名とあっているか
- AlexaシミュレーターのJSONデータを使ってテストする
- CloudWatchのログを見る
Alexaシミュレーターで吐き出されるJSONをLambda関数のテストデータとして使えるのが便利です。何度も使いました。
あとは、ソース中でConsole.log()すると、CloudWatchで見れるのでそれも役に立ちます。
Alexaアプリ(スマホ)
- 自分の言葉が正しく認識されているか
Alexaaアプリの履歴で話した言葉がどう認識されたか見れるので、確認しましょう。
MQTTは便利
MQTTは便利です。同じ仕組みで他の機器からも簡単に操作できます。AWS IoTボタンもいけそうですね。でもボタンを押すくらいなら、そもそもリモコン使えって話だけど…
追記
同じことをスマートホームスキルでやってみました。こちらは”ラズパイで”とつけなくていいので便利です。
- アイリスオーヤマ(IRIS OHYAMA)