※ この記事はGoogle Homeで子供の朝支度を促してみるで書ききれなかった内容の補足となります
はじめに
子供の通う幼稚園ではバスキャッチが導入されています。「まもなくバスが到着します」とメール通知してくれる、とても便利なシステムで、Web経由でもバスの現在位置や、到着までの時間を確認できます。ただ、意外とバタバタする朝に、ちょくちょくスマホで確認するのはちょっと...、というわけで、Google Homeを利用してみることにしました。
やりたいこと
バスの到着までの時間をGoogle Homeに読み上げて欲しい。
- 「ねぇ Google、幼稚園バスは?」に対して「あとxx分で到着予定」
- Google Homeへ話しかけなくても、指定時刻になったら「あとxx分で到着予定」
どうやったか
処理フロー
処理フローは、Google Home/Googleスプレッドシート→Firebase→ラズパイ→Google Homeとしました。Firebaseへの入力を2系統(音声入力:Google Home→Firebase、定時処理:Googleスプレッドシート→Firebase)とし、以降の処理は共通利用しています。
定時処理(Googleスプレッドシート→Firebase)については以前の記事で触れていますので、この記事では音声入力以降の処理(Google Home→Firebase→ラズパイ→Google Home)について書いていきます。
IFTTT
まず、「幼稚園バスは?」と話しかけられたことを、IFTTTを利用して、Firebaseへ通知します。
Google Assistantレシピ
thisとしてGoogle AssistantのSay a simple phraseを指定します。

Webhooksレシピ
thatではWebhooksを利用します。
FirebaseのDatabaseは以下のような構成にしています。通知内容をwordの値として書き込み、その更新イベントをラズパイでフックします(フック処理は次項で触れます)。
プロジェクト名-xxxxx
└ googlehome
└ word: ""
そのためWebhooksは以下のように指定しました。

Raspberry Pi
firebase
続いて、Firebaseへの書き込みを、Realtime Databaseの更新イベントをフックしているラズパイで受け取る部分です。以前の記事でも参照した実装(拡張性があって助かります)へ分岐処理を追加し、スクレイピングを行う別プロジェクトを起動します。
var firebase = require("firebase");
var config = {
//省略
};
firebase.initializeApp(config);
const path = "/googlehome";
const key = "word";
const db = firebase.database();
db.ref(path).on("value", function(changedSnapshot) {
const value = changedSnapshot.child(key).val();
if (value) {
const words = value.split(" ");
const command = getJsonData(words[0], {
...
//バスキャッチ
"buscatch": () => {
return "sh /home/pi/buscatch/index.sh";
},
...
})();
if (command) {
const exec = require('child_process').exec;
exec(command);
db.ref(path).set({[key]: ""});
}
}
});
Webスクレイピング
バスキャッチのユーザには、バスの運行状況などを確認できるURLが発行されます。運行時のWebページの内容が、
...
<div id="announce">
<span>[本日の運行]:<font style="color:#xxxxxx;">◆</font>xx:xx登園<br />通常通り運行しております<br />
あとxx分で到着予定
</span>
</div>
...
といった感じでしたので、スクレイピング処理は以下のようにしました。
var https = require("https");
var iconv = require("iconv");
var select = require('soupselect').select;
var htmlparser = require("htmlparser");
var options = {
hostname: 'xxxxx.xxx',
port: 443,
path: '/xxxxx/?x=xxxxx',
method: 'GET'
};
var req = https.request(options, (res) => {
res.setEncoding('binary');
var body = '';
res.on('data', (chunk) => {
body += chunk;
});
res.on('end', () => {
conv = new iconv.Iconv('Shift_JIS', 'UTF-8//TRANSLIT//IGNORE')
body = new Buffer(body, 'binary');
body = conv.convert(body).toString();
var handler = new htmlparser.DefaultHandler((err, dom) => {
if (err) {
console.log("ERROR: " + err);
} else {
var span = select(dom, 'div#announce span');
if (span.length > 0) {
var msg = '';
if (span[0].children.length == 7 &&
span[0].children[4].type == 'text' &&
span[0].children[5].type == 'tag' && span[0].children[5].name == 'br' &&
span[0].children[6].type == 'text') {
var status = span[0].children[4].raw;
var time = span[0].children[6].raw;
msg += time + '、';
msg += status;
msg = msg.replace(/\r?\n/g, '');
}
if (msg.length == 0) msg = 'バスの運行状況を取得できませんでした';
console.log('MESSAGE: ', msg);
//省略(msgをgoogle-home-notifierで読み上げ)
}
}
});
var parser = new htmlparser.Parser(handler);
parser.parseComplete(body);
});
});
req.on('error', (e) => {
console.log('ERROR: ', e.message);
});
req.end();
上記Webページの内容をスクレイピングすると、msgの内容は「あとxx分で到着予定、通常通り運行しております」となります。
この実装を、以下のシェルスクリプト経由で実行しています。
# !/bin/sh
cd `dirname $0`
node index.js
なお、省略したGoogle Home(google-home-notifier)での読み上げ処理は、以前の記事のものを利用しています。
おわりに
バスの到着時刻は日によって意外と前後するので、定時の確認だけでなく、「幼稚園バスは?」でも状況を確認できることが、思いのほか便利でした。お休みの子が多いときには、「え?ワープした?」というくらい、到着予定が繰り上がっていくこともありますし。まぁ、もっと余裕をもって支度をすればよいだけなのですけど、それは何故か難しいので...。