JavaScript
Firebase
IoT
おうちハック
GoogleHome

WebからGoogle Homeを喋らせたり家電操作したりしてみる

はじめに

この記事は スマートスピーカー Advent Calendar 2017 19日目の記事です。
本記事を書いていたらたまたま空きができていたので急遽投稿させて頂きます。

過去記事(Google Homeでやったことまとめ)にて構築したGoogle Homeによる家電操作環境に一手間加え、Webからも操作できるようにしてみました。
家電操作はもちろん、外からGoogle Homeを喋らせたりできます。

image.png

家電の操作には自宅に設置してあるラズパイを使用していますが、グローバル→ローカルのネットワーク通信にFirebaseのRealtime Databaseを利用しています。
Firebaseは他にも様々な機能を提供しており、その一つにHostingがあります。
参考:まだ間に合う!着々と進化しているFirebaseをまとめてみるよ #Firebase #FJUG(全機能まとめ)

Hostingを使用する事でSSL対応したWebページを簡単に設置できるうえ、Realtime Databaseやその他のFirebaseの機能とも簡単に連携を行えます。

なお前提として以下のいずれかの記事の環境を構築している必要があります。
※Google Home→IFTTT→Firebase→ラズパイの流れができていればおっけーです。
 この構成は現状Google Homeからの家電操作の鉄板構成だと思います。

処理の流れ

image.png

実際の動作

動画をご覧頂くとお分かりになると思いますが、電気操作のとことかレスポンスめっちゃ速いですよね。
Wi-Fiつけたままだとローカル間通信を疑われるレベルな気がしたので、最初に明示的にLTEに切り替えています。
なのでちゃんとグローバルネットワークを経由しています。Realtime Databaseやばし。

またネイティブアプリっぽい動作をしていますが、中身はただのWebページです。
PWA(Progressive Web App)という仕組みを使うことでこのようにネイティブアプリっぽく見せかけることができます。

なお動作は以下の環境で確認しています。

  • Windows10 Firefox
  • Windows10 Chrome
  • Android Chrome

作り方

それでは作り方を説明していきます。
と言っても前提環境が整っていればHTMLソースをFirebase Hostingへデプロイするだけです。

ソース

GitHubにあげてあります。
home-controller

軽めに作ろうとVanilla JSで書いています。
参考:Vanilla JS 入門

外から家電を操作するときや連続操作を行うときぐらいしか使わないので適当にボタンを置いてさくっと作ってたのですが、

  • せっかくだしレスポンシブでちゃんと見えるようにしよう
  • スマホだとタブ下側のほうがいいな
  • (実装したあと)やっぱ上でもいいや。けどせっかく作ったし設定で保持して変えられるようにしよう
  • スワイプもしたいな
  • テキストボックスに自由に入れた言葉をGoogle Homeに喋らせよう
  • ブラウザのタブ消費するのうざいからPWAでアプリっぽく見せよう
  • ボタン押したときトースト表示させたら分かりやすいかな

と、どうでもいい実装が増えてしまいました。

レイアウトのカスタマイズ

ボタンの増減やレイアウトを変更したい場合はdata.jsをいじります。

data.js
const data = [
    {
        "name": "電気",
        "buttons": [
            {"name": "A", "command": "light a", "column": "2"}, 
            {"name": "B", "command": "light b", "column": "2"}, 
            {"name": "C", "command": "light c", "column": "2"}, 

            ...

            {"column": "3"},
            {"name": "↑", "command": "ps4 上", "column": "3"}, 
            {"column": "3"},
            {"name": "←", "command": "ps4 左", "column": "3"}, 
            {"name": "↓", "command": "ps4 下", "column": "3"}, 
            {"name": "→", "command": "ps4 右", "column": "3"}, 

            ...
  • name: ボタン上の表示名
  • command: Firebaseに書き込むコマンド
  • column: 1行に対しの何個分のボタン幅をとるか
    • 横幅に対し1なら100%、2なら50%、3なら33.3%
    • columnのみを指定すると空スペースが入ります

デプロイ

まずはローカルにFirebaseプロジェクトを作成します。
新規ディレクトリまたは過去に作成したGoogle Home操作用のプロジェクトディレクトリで以下のコマンドを実行します。

> firebase init hosting

色々質問されるので以下のように答えていきます。

  • ? Are you ready to proceed? (Y/n)
    • そのままEnter
  • ? Select a default Firebase project for this directory:
    • Google Home用のRealtime Databaseと同じプロジェクトを選択
  • ? What do you want to use as your public directory? (public)
    • 公開ソースのディレクトリ名を指定
      • なんでもよければそのままEnter
  • ? Configure as a single-page app (rewrite all urls to /index.html)? (y/N)
    • SPAにするかどうか
      • なんでもよければそのままEnter

デフォルトではpublicディレクトリが作成されるので、配下のソースを上書いてください。

そして以下のコマンドでデプロイします。

> firebase deploy

コマンドの結果としてURLが返ってきますので、そこにアクセスすればおっけーです。

また動画のようにスマホでアプリっぽく使うには、ブラウザのメニューから「ホーム画面に追加」ってやってみてください。

Google Homeを喋らせる

過去記事にて「google-home-notifier」をご紹介していましたが、Firebaseとの連携部は記事にしていなかったため念のため。

google-home-notifierの導入は以下の記事をご覧ください。
GoogleHomeスピーカーに外部からプッシュして自発的に話してもらいます
Google Homeで時報を知らせる

次に過去記事にてラズパイ側に作成したindex.jsへ以下のよう追記することでRealtime Databaseに書き込んだメッセージをGoogle Homeで喋らせられます。

index.js
...

//google-home-notifier
const googlehome = require("./google-home-notifier")
//Google Home Device Set
googlehome.device("デバイス名を入力")

...

//database更新時
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) {
    console.log(value)

    //コマンド生成
    const command = getJsonData(value.split(" ")[0], {

      ...

      //google home notifier
      "notifier": () => {
        return () => googlehome.notify(value.replace("notifier ", ""))
      },

      ...

      //default
      "default": () => false,

    })()
    console.log(command)

    //コマンド実行
    if (command) {
      //typeof
      if (typeof command === "string") {

        ...

      } else if (typeof command === "function") {
        command()
      }

      //firebase clear
      db.ref(path).set({[key]: ""})
    }

  }
})

技術的な補足

Firebase

ソースを見ていただくとお分かりになると思いますが、Realtime Databaseの認証情報がどこにもありません。
ソースをRealtime Databeseと同じプロジェクトのHostingに置くことで勝手に連携してくれるのです。

このときHTML側にHosting用のFirebaseライブラリを読み込んでおきます。
ちなみにコメントアウトしていますがfirebase-auth.jsとかも有効にすれば認証機能とかも使えると思います。
(すみません、試してません…)

あと私の記事に沿ったまま構築していた場合、Realtime Databaseへの書き込みルールがガバガバなためセキュリティに注意してください。
第三者にURL等にあるFirebaseのプロジェクトIDが知られてしまうと、家の家電を操作され放題になりますので…

PWA(Progressive Web App)

動画ではアイコンから起動して、アドレスバーもないアプリっぽい見た目になっていますね。
これはPWA(Progressive Web App)という仕組みを使っています。
参考:いまさら聞けないPWAとAMP

PWAをざっくり説明すると、Webアプリをスマホのネイティブアプリっぽく動かそう!っていう仕様です(多分)。
ネイティブアプリっぽく見せるにあたりブラウザがサポートするService Workerという仕組みを使ってキャッシュによるオフライン動作やプッシュ通知なんかもできるようになります。

現在iOS系ブラウザのエンジンとなるWebkitがService Workerに対応中のため現状ではAndroidのみの対応となりますが、Webkitも対応したら一つのソースでAndroid/iOSに対応できるというメリットが生まれます。

似たような仕組みで言うと今は亡きChromeアプリや、マイナーですがHTA(HTML Applications)なんかでしょうか。
HTAはエンジンがIEというのがなんとも言えないとこですが、WindowsならちょっとしたGUIツールをさくっと作れて便利です。
(htmlの拡張子をhtaにするだけで動きますし、FileSystemObjectとかでローカルファイル操作しても警告が出ません)

ちなみにアイコン置いてアプリっぽく見せるだけであればService Workerは不要だったのでiOSでもいけるんじゃないかと思います。

今回私がやったのもmanifest.jsonを置いてindex.htmlから参照させただけです。
manifest.jsonWeb App Manifest Generatorから簡単に生成できます。

Vanilla JS

ジョークフレームワークです。
ただのネイティブJavaScriptです。

ネイティブでも色々できるっちゃできますが、スワイプやトーストのライブラリをググってみてもjQueryプラグインだったりで、結局自分で実装することになり面倒でした…

おわりに

使う場面としてはこの時期だと家に帰る前にエアコンをつけておいたり、出かけたとき家電をちゃんと消したか怪しかったらオフらせたりとかです。
家の鍵なんかもIoT化して外出先で制御できたら安心ですね。

あとは会社から帰宅するときに「パパこれから帰るよ」とGoogle Homeに喋らせたり、自宅に近づいたらPCを起動させたりしています。
このあたりはAndoridの自動化アプリ「Tasker」で会社を出たときや家に着いたときに勝手にトリガーさせてもいいかなと思っています。