Node.js
RaspberryPi
awsIoT
linebot
GoogleHome

GoogleHomeとLINEを使って妻にあったか〜い料理を用意してもらう方法

■ はじめに

僕は基本的に帰宅する際は必ず「今から帰りまーす!」とLINEで妻にメッセージを送ります。
LINEに気づいて貰えれば夕飯などのやりとりができるので良いですが、
家につくまで気づかないという日も少なくありません。。

少しでも気づいてもらえる仕組みを作り、日々温かい料理にありつきたい!
そんな切実な目的の為に自分なりに奮闘した記録です。

理想 ▶ 何かしらレスポンスがある事

p1

■ アイデア(つくるもの)

通常のLINEメッセージ(文字)と同時に、GoogleHome(音声)でLINEメッセージの内容を読み上げさせる。シンプル!

illust01.png

■ ポイント

  • Line ClovaやAmazon Alexaと違い、GoogleHomeはAPI経由でユーザアクション無しで喋らせる事ができます。(っていう認識。)
  • 受け手は何も気にせず今まで通り生活を送るだけでOK!
  • スマホの通知に気づきにくい状況においては強力な仕様です。

■ 使うもの

  • GoogleHome
  • LINE BOT
  • RaspberryPi3
  • AWS EC2(nodeJS)
  • AWS IoT
  • wifiルーター
  • google-home-notifier.js

■ 構成図

 一つ一つの工程は非常にミニマムです。
(当初HerokuとIFTTTなどの無料環境で試みていましたが、業務で使いそうなAWSに変更しました。)

illust02.png

1.LINE BOTを作る

LINE BOTと言っても今回は大げさな機能は持ちません。
LINEメッセージ(テキスト)をエンドポイント(WebAPI)にPOSTするだけです。
基本的な設定は管理画面だけで済みます。

1-1. LINE developerにログイン

p1

1-2. プロバイダー(サービス提供者)を選択

  • 「+」ボタンで追加して入力
  • 「次のページ」ボタンを選択

p2

1-3. Messaging APIの情報を入力

  • アプリアイコンや名前など必要項目を埋める
  • プラン選択ではユーザが家族限定なのでDeveloper Trialを選択
  • 「確認」ボタンを選択

p3

1-4. 確認画面

  • 確認してOKならチェックいれて「作成」

1-5. Botアプリの基本設定

これで一旦BOTアプリが作れました。
アプリのサムネイルを押してもう基本設定を変えていきます。

p4

メッセージ送受信設定を変える

  • アクセストークン → 「再発行」
  • Webhook送信 → 「利用する」
  • Webhook URL → エンドポイント(WebAPI)のURL(無ければ準備して後で設定してあげればOKです。HTTPS必須。)
  • Botのグループトーク参加 → 「利用する」
  • 自動応答メッセージ → 特にいらないので 「利用しない」
  • 友達追加時あいさつ → 特にいらないので 「利用しない」

p5

1-6. セキュリティ管理の設定(左のメニュー)

BOTアプリからエンドポイントを叩く時は、
事前にIPアドレスをホワイトリストとして追加してあげる必要があります。

  • IPアドレスとCDIRを「24」に設定

こんなサービスで割り出せます。
CMAN

p6

これでBOTの設定はOK!

1-7. BOTと友達になる

管理画面にQRコードが生成されているので、自身のスマホのLINEアプリから読み取って友達追加します。
※この友達(Bot)にメッセージを送った時のみ、GoogleHomeがその内容を喋ります。

p7

2. AWS IoTのSDKをダウンロード

サーバとラズパイのデータ通信はPub/Subモデルが適切です。
AWS IoTでMQTTの仕組みを利用すれば簡単に実現できます。
MQTTはIoTで非同期通信を行う際には軽量で最適なプロトコルです。

2-1. AWS マネジメント コンソールにアクセス

pp1

2-2.SDKを選択

  • Linux/OSX環境上のNode.jsを選択
  • 「次へ」ボタン

pp2

2-3. モノの登録

  • 自分は適当にGoogleHomeChanとしてみました。
  • 「次のステップ」ボタン

pp3

2-4. 接続キットのダウンロード

ここで発行される証明書やプライベートキー、選択したSDKなどが確認できます。
(このあとで内包されるstart.shのシェルを叩くと必要なファイルが一式揃います)

  • 「接続キットのダウンロード」ボタン(connect_device_package.zip)
  • 「次のステップ」ボタン

pp4

2-5. 接続テスト

先程ダウンロードしたファイルの確認と、シェルを実行して必要なファイルを生成します。
※デスクトップなどにzipがあった場合、解凍後にファイルがバラけるので、フォルダに格納しておいた方が無難です。

  • zip解凍 → シェルの権限変更 → シェル実行
$ unzip connect_device_package.zip
$ chmod +x start.sh
$ ./start.sh

pp5

解凍後はモノで設定したネーミングがついたファイル名で生成されます。
※自分の場合はGoogleHomeChan.cert.pemといった具合です。

  • xxxx.cert.pem
  • xxxx.private.key
  • xxxx.public.key
  • start.sh

シェルを叩くと必要なnodemoduleなどが生成されます。

  • ./node_modules
  • root-CA.crt

そのまま画面を進むと、
接続がうまくいったとの画面になり、一旦AWS IoT側の設定は終了です。

3.LINE BOT - エンドポイント - AWS IoTの連携

AWS IoT関連の必要なファイルは揃ったので、
それぞれ繋ぎこむプログラムを作成していきます。

自分はEC2環境にexpressで作ったnodejs環境があったので、それをAPIサーバとして活用しています。
また注意点としてSSLを導入していないとLINEからアクセスは許可されないので、事前に準備します。

無料のSSL発行サービスもあります
Lets Encrypt

3-1. パブリッシャーを実装

  • publish.js(ウェブサーバ上で実行されるパブリッシャー)
  • subscribe.js(ラズパイ上で実行されるサブスクライバー ※後のラズパイの方でコードを載せます)

まず先にエンドポイントからAWS IoTに接続してデータを送るpublish.jsを作成します。
AWS IoT関連のファイル一式をフォルダごと配置しておくと良いです。

./connect_device_package/publish.js
// AWS IoT DeviceSDKの利用
var awsIot = require('aws-iot-device-sdk');

// 秘密鍵、証明書などの設定
var device = awsIot.device({
  region: '<管理画面から取得>',
  host: '<管理画面から取得>',
  clientId: 'GoogleHomeChan-client',
  privateKey: 'GoogleHomeChan.private.key',
  clientCert: 'GoogleHomeChan.cert.pem',
  caCert: 'root-CA.crt',
});

var count = 0;

// 通信確立した際に呼び出されるイベント
device.on('connect', function() {
  console.log('connect');
  setInterval( function() {
    count++;
    var led = count * 2;
    console.log(led.toString());
    device.publish('topic_1', led.toString());
  }, 1000);
});

※region , hostは先程作成したIoTのモノの管理画面から確認できます。
アプリを選択し、「操作」からHTTPS欄で記載されているアドレスです。
region:us-west-2(自分の場合)
host:一行まるっと

pp6

3-2. エンドポイント(API)を実装

LINE Botで設定したWEBフック先を実装します。
LINE BOTからPOSTされたテキストデータを、AWS IoTへ送信します。

  • Api.jsとして新規作成し、先程作成したpublish.jsを読み込んで実装
  • 実際にGoogleHomeに読み上げもらうテキストをここで良い感じに整形。(単語の読みの間を「、」で調節していい感じに。。良いやり方があるのでしょうか。。?)
./Api.js
var Publish=require('./connect_device_package/publish');//上記作成したpublish.js

var lineTxt = req.body.events[0].message.text;
var token='<LINEで発行したアクセストークンを入れる>';
var textmain='';

if (req.body['events'][0]['source']['type'] == 'user') {
  // ユーザIDでLINEのプロファイルを検索して、ユーザ名を取得する
  var user_id = req.body['events'][0]['source']['userId'];
  var get_profile_options = {
    url: 'https://api.line.me/v2/bot/profile/' + user_id,
    json: true,
    headers: {
      'Authorization': 'Bearer '+token
    }
  };
  request.get(get_profile_options, function(error, response, body) {
    if (!error && response.statusCode == 200) {
      var displayName=body['displayName'];
      textmain = displayName+"さん、、から、、Google Homeちゃんに、、ラインでメッセージが届きました。内容は、、、"+lineTxt+'、、です。';
      Publish.send(textmain);
    }
  });
//BOTのチャットを複数名で共有すると、発言者のユーザ名が取れない仕様なので、BOT名+メッセージとしてみた
} else if ('room' == req.body['events'][0]['source']['type']) {
  textmain="Google Homeちゃん、、から、、ラインでメッセージが届きました。内容は、、、"+lineTxt+'、、です。';
  Publish.send(textmain);
  callback('みなさまから送られた文章の\n');
}

4. RaspberryPiの設定

ラズパイはAWS IoTから通知(データ)を待ち受け、
それを受け取ったらローカルネットワーク上のGoogleHomeへ投げる役目です。

4-1.下準備

  • ラズパイもNode.JSが実行できる環境を整えておきます。
  • AWS IoT関連のファイルをコピーしてプログラムのルートディレクトリに配置します。(node_modules,xxx-pem,xxx-key,xxx-crt)

4-2. サブスクライバーを実装

./subscribe.js(暫定版)
var awsIot = require('aws-iot-device-sdk');
var sleep = require('sleep');
var pin = 4;

var device = awsIot.device({
  region: '<publish.jsと同じ>',
  host: '<publish.jsと同じ>',
  clientId: '<publish.jsと同じ>',
  privateKey: '<publish.jsと同じ>',
  clientCert: '<publish.jsと同じ>',
  caCert: '<publish.jsと同じ>',
});

// 通信確立時
device.on('connect', function() {
  console.log('connect');
  device.subscribe('topic_1');
  // すぐに書き込もうとすると権限エラーのためsleepさせる
  sleep.sleep(1);
});

// subscribeした時のイベント
device.on('message', function(topic, payload) {
  console.log('message', topic, payload.toString());
  //ここにGoogleHomeに接続するプログラムを書く 

});

process.stdin.resume();

// Ctrl+Cによって終了する場合の処理
process.on('SIGINT', function() {
  console.log('receive SIGINT signal');
  process.exit();
});

4-2. GoogleHomeに接続する

上記サブスクライバーにはGoogleHomeとの接続コードが抜けています。
接続するには、簡単便利なモジュール(google-home-notifier.js)があるのでそれを利用します。
ルートディレクトリでダウンロードします。

google-home-notifier.js

※ラズパイで使う場合に別途必要となるモジュールなども記載されているので、
全てreadmeの手順通りにインストールすれば動作する環境が整います。

IP指定を追記する

本体やモジュール諸々完了したら、以下ファイルでIP指定をします。

./google-home-notifier/google-home-notifier.js(20行目辺りに一行追加)
//接続先IPアドレス
deviceAddress='192.168.2.xxx';

GoogleHomeの「IPアドレス」と「Macアドレス」は、スマートフォンアプリで簡単に調べられます。
「デバイス」 ▶ 「設定」 ▶ 一番下の画面

ip1.jpg

これでgoogle-home-notifierの準備が整いました。

./subscribe.js(完全版)
var awsIot = require('aws-iot-device-sdk');
var sleep = require('sleep');
var pin = 4;

var googlehome = require('google-home-notifier');
var language = 'ja';
googlehome.device('Google Home',language);

var device = awsIot.device({
  region: '<publish.jsと同じ>',
  host: '<publish.jsと同じ>',
  clientId: '<publish.jsと同じ>',
  privateKey: '<publish.jsと同じ>',
  clientCert: '<publish.jsと同じ>',
  caCert: '<publish.jsと同じ>',
});

// 通信確立時
device.on('connect', function() {
  console.log('connect');
  device.subscribe('topic_1');
  // すぐに書き込もうとすると権限エラーのためsleepさせる
  sleep.sleep(1);
});

// subscribeした時のイベント
device.on('message', function(topic, payload) {
  console.log('message', topic, payload.toString());
  //ここにGoogleHomeに接続するプログラムを書く 
  googlehome.notify(payload.toString(),function(res){console.log(res)});

});

process.stdin.resume();

// Ctrl+Cによって終了する場合の処理
process.on('SIGINT', function() {
  console.log('receive SIGINT signal');
  process.exit();
});

4-3. 実行!

subscribe.jsを実行します。

$ node subscribe.js

もしかするとWARNINGがたくさんでるかもしれませんが、
「connect」とconsoleが表示されていれば通信が確立されています。
ラインでBOTアプリにメッセージを投げてみます。
一旦これで動作する想定です。

5. GoogleHomeのDHCP問題

GoogleHomeは固定IPで運用できない仕様になっているようです。
その為、ラズパイからローカルネットワークでアクセスする際に、
NodeJS(google-home-notifier.js)で指定したIPアドレスでは、
DHCPによりIPアドレスの割り振りが変わる事で参照エラーを起こします。
自宅のwifiルーターの設定を調整する事で、固定IPに近い挙動を実現させ、それを解決します。

5-1. GoogleHomeアプリで「IPアドレス」と「Macアドレス」を取得

ここでもう一度GoogleHomeのスマートフォンアプリにて、
現在のIPアドレスとMACアドレスを確認します。

5-2. wifiルーターの設定

利用しているwifiルーターにログインし、
先程のMACアドレスに対して任意のIPアドレス(今回は先程のメモのIP)を割り当てます。
こうする事でGoogleHomeに対して優先的にそのIPが割り振られる為、
擬似的に固定IPのような運用が可能になります。
ルーター設定については各自のメーカーサイトで調べてください。

完了したら、もう一度プログラムを実行して、LINEアプリに投稿して動作確認します。

$ node subscribe.js

5-3. 動的検出

google-home-notifierには、ネットワーク上の喋れるデバイス名とIPアドレスを動的に検出し、
そのデバイス名を利用して接続できる仕組みが備わっているようです。
この場合、今回の様にIPアドレスを指定する必要もないのでスマートです。
本来であれば公式のサンプルのコードをそのまま使えば動作するっぽいのですが、
自分の環境でうまく動かせなかったので、今回はIP指定する方法を選択しました。

6. Node.jsを永続的に稼働させる

Node.jsがダウンする事もあるのでdaemon化しておく事と安心です。
foreverなどを利用してプロセスを復帰できるようにして事をおすすめします。

インストール
$ sudo npm install -g forever
実行
$ forever subscribe.js

forever.js

7. 最後に…

結局、今一番活躍している場面は、
我が家の幼稚園生の子供がダラダラしてて歯磨きをしない時です。
GoogleHomeに言ってもらうと、親より言う事聞きます。。(汗)

GoogleHome:「GoogleHomeちゃんからラインでメッセージが届きました。早く歯磨きしないと怖い鬼が来ます。急ぎましょう!」
子供:「きゃーー!ママ早く歯磨き!!」