Google Homeで幼稚園バスの運行状況を確認する

※ この記事は[Google Homeで子供の朝支度を促してみる](https://qiita.com/hideo-works/items/db25a748889d47d5eff2)で書ききれなかった内容の補足となります

# はじめに

子供の通う幼稚園では[バスキャッチ](http://www.buscatch.com/)が導入されています。「まもなくバスが到着します」とメール通知してくれる、とても便利なシステムで、Web経由でもバスの現在位置や、到着までの時間を確認できます。ただ、意外とバタバタする朝に、ちょくちょくスマホで確認するのはちょっと...、というわけで、Google Homeを利用してみることにしました。

# やりたいこと

バスの到着までの時間をGoogle Homeに読み上げて欲しい。

- 「ねぇ Google、幼稚園バスは？」に対して「あとxx分で到着予定」
- Google Homeへ話しかけなくても、指定時刻になったら「あとxx分で到着予定」

# どうやったか

## 処理フロー

処理フローは、Google Home/Googleスプレッドシート→Firebase→ラズパイ→Google Homeとしました。Firebaseへの入力を2系統（音声入力：Google Home→Firebase、定時処理：Googleスプレッドシート→Firebase）とし、以降の処理は共通利用しています。

![ProcessFlow.png](https://qiita-image-store.s3.amazonaws.com/0/60193/fbc13e5e-7dc7-5ef8-3a6f-9f3392ff567c.png)

定時処理（Googleスプレッドシート→Firebase）については[以前の記事](https://qiita.com/hideo-works/items/db25a748889d47d5eff2#google%E3%82%B9%E3%83%97%E3%83%AC%E3%83%83%E3%83%89%E3%82%B7%E3%83%BC%E3%83%88)で触れていますので、この記事では音声入力以降の処理（Google Home→Firebase→ラズパイ→Google Home）について書いていきます。

## [IFTTT](https://ifttt.com/)

まず、「幼稚園バスは？」と話しかけられたことを、IFTTTを利用して、Firebaseへ通知します。

### Google Assistantレシピ

thisとしてGoogle AssistantのSay a simple phraseを指定します。

<img width="197" alt="GoogleAssistant.png" src="https://qiita-image-store.s3.amazonaws.com/0/60193/b151fdbf-054f-7201-2a90-8fc8914a7d76.png">

### Webhooksレシピ

thatではWebhooksを利用します。

FirebaseのDatabaseは以下のような構成にしています。通知内容をwordの値として書き込み、その更新イベントをラズパイでフックします（フック処理は次項で触れます）。

```
プロジェクト名-xxxxx
 └ googlehome
 　 └ word: ""
```

そのためWebhooksは以下のように指定しました。

<img width="197" alt="Webhooks.png" src="https://qiita-image-store.s3.amazonaws.com/0/60193/fd7f9522-5835-90fa-4cb8-99dbbbdafcfe.png">

## Raspberry Pi

### [firebase](https://www.npmjs.com/package/firebase)

続いて、Firebaseへの書き込みを、Realtime Databaseの更新イベントをフックしているラズパイで受け取る部分です。[以前の記事](https://qiita.com/hideo-works/items/db25a748889d47d5eff2)でも[参照した実装](https://qiita.com/miso_develop/items/c2008235cb4a8567cfa9#nodejs%E3%81%AE%E5%AE%9F%E8%A3%85)（拡張性があって助かります）へ分岐処理を追加し、スクレイピングを行う別プロジェクトを起動します。

```js:firebase/index.js
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ページの内容が、

```html
...
<div id="announce">
<span>[本日の運行]：<font style="color:#xxxxxx;">◆</font>xx：xx登園<br />通常通り運行しております<br />
あとxx分で到着予定
</span>
</div>
...
```

といった感じでしたので、スクレイピング処理は以下のようにしました。

```js:buscatch/index.js
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分で到着予定、通常通り運行しております」となります。

この実装を、以下のシェルスクリプト経由で実行しています。

```sh:buscatch/index.sh
#!/bin/sh
cd `dirname $0`
node index.js
```

なお、省略したGoogle Home（google-home-notifier）での読み上げ処理は、[以前の記事](https://qiita.com/hideo-works/items/db25a748889d47d5eff2#google-home-notifier)のものを利用しています。

# おわりに

バスの到着時刻は日によって意外と前後するので、定時の確認だけでなく、「幼稚園バスは？」でも状況を確認できることが、思いのほか便利でした。お休みの子が多いときには、「え？ワープした？」というくらい、到着予定が繰り上がっていくこともありますし。まぁ、もっと余裕をもって支度をすればよいだけなのですけど、それは何故か難しいので...。