Node-REDのWebSocketノードを使って、WAN側からGoogle Homeを喋らせる

Blog

Node-Redサーバーで、node-red-contrib-castノードを使うと、簡単にGoogle Homeで任意のメッセージを喋らせることができます。 ただし、Google HomeはLAN側にいるためNode-REDサーバーはローカルに立てる必要があり、外(WAN側)から操作できません。

これを解決するのに、Herokuに立てたWebSocketサーバーとローカルに立てたNode-REDサーバーのWebSocketノードを通信させることにしました。

システムの構成は下図のようになります。 構成図

動作を確認した環境

  • Node-RED v0.20.5
  • Node.js v12.14.0
  • Heroku / Heroku CLI v7.38.1
  • Google Home
  • Google Homeアプリケーション(iPhone)

WebSocket ServerをHeroku上に立てる

Using WebSockets on Heroku with Node.jsを参考に、Heroku上にWebSocket Server(とブラウザから入力を受け付けるフォームのためのWebServer)を立てます。

  1. プロジェクトのルートでnpmを初期化します。

    npm init -y
    
  2. package.jsonに以下を追記します。

      "engines": {
        "node": "12.x"
      },
      "scripts": {
        "start": "node server.js"
      }
    
  3. WebServerを立てるためのExpressとWebSocket Clinet Serverのws(といくつかmodules)を追加します。

    npm install --save express
    npm install --save ws bufferutil utf-8-validate
    
  4. 次の内容で、server.jsを作成します。

    'use strict';
    
    const express = require('express');
    const { Server } = require('ws');
    
    const PORT = process.env.PORT || 3000;
    const INDEX = '/index.html';
    
    // WebServer を作成
    const server = express()
      .use((req, res) => res.sendFile(INDEX, { root: __dirname }))
      .listen(PORT, () => console.log(`Listening on ${PORT}`));
    
    // WebSocket Sever を作成
    const wss = new Server({ server });
    
    // message イベントでブラウザから受け取ったメッセージを WebSocket で通信しているクライアントに送信する
    wss.on('connection', (ws) => {
      console.log('Client connected');
      ws.on('message', (message) => {
        sendAll(message);
      });
      ws.on('close', () => console.log('Client disconnected'));
    });
    
    const sendAll = (message) => {
      wss.clients.forEach((client) => {
        client.send(message);
      });
    }
    
  5. テキストボックスとボタンを配置した、index.htmlを作成します。

    <!DOCTYPE html>
    <html>
      <head>
        <script>
          const HOST = location.origin.replace(/^http/, 'ws')
          const ws = new WebSocket(HOST);
          ws.onmessage = (event) => {
            const el = document.getElementById('server-message');
            el.innerHTML = 'Message: ' + event.data;
          };
        </script>
        <title>WebSocket Server</title>
      </head>
      <body>
        <input type="text" id="text" value="" name="text">
        <input type="submit" id="button" value="送信する">
        <p id="server-message"></p>
      </body>
      <script>
        document.getElementById('button').addEventListener('click', ()=> {
          const text = document.getElementById('text').value;
          ws.send(text);
        })
      </script>
    </html>
    
  6. ローカルで動作確認します。

    npm start
    

    ブラウザでhttp://localhost:3000を開き、入力フォームにメッセージを入れて送信します。

    送信すると、「Message: 入力したメッセージ」が表示されます。また、開発者ツールのネットワークタブで、[WS]でフィルターすると、WebSocket通信のキャプチャを確認できます。

  7. Gitを初期化し、node_modules以下を.gitignoreに追加します。

    git init
    echo '*node_modules' >> .gitignore
    
  8. ディレクトリ次の内容でコミットを作り、Herokuにpushします。APP_NAMEはHerokuのアプリケーション名で、任意の値を設定します。

    git add .
    git commit -am "first commit"
    heroku create APP_NAME
    git push heroku master
    
  9. Heroku上にできたアプリケーションで、ローカルでやったのと同様に動作確認をします。

    heroku open
    

    このとき、開発者ツールのネットワークタブで、[WS]でフィルターし、キャプチャしているWebSocket通信の[Name]を右クリック >[Copy]>[Copy link address]で WebSocket の通信先 URL をコピーしておきます。

Google Homeの設定を行う

Google Homeと接続したスマートフォンのGoogle Homeアプリケーションを開き、設定画面から以下を行います。

  • 言語設定を日本語にする
  • 設定画面の最下部にあるIPアドレスを確認する

Node-REDサーバーをローカルに立てる

  1. Node-REDをローカルにインストールします。

    npm install -g node-red
    
  2. Node-REDを起動します。

    node-red
    
  3. http://127.0.0.1:1880/にアクセスし、Node-REDの起動を確認します。

WebSocketノードとCastノードを追加する

  1. Node-REDの画面で、右上のハンバーガーメニューから[パレットの管理]をクリックします。

  2. [ノードの追加]タブを選択し、「node-red-contrib-cast」を検索してインストールします。

  3. 左サイドメニューから、次のノードをエディタ上にドラッグ&ドロップします。

    <dl> <dt>入力</dt><dd>WebSocket</dd> <dt>機能</dt><dd>キャスト</dd> </dl>

  4. WebSocketノードは次のように設定します。

    項目
    種類接続
    URL開発者ツールのネットワークタブで確認した、WebSocket の通信先 URL
    送信/受信ペイロードを送信/受信
  5. キャストノードは次のように設定します。

    項目
    IPGoogle Home の IP アドレス
    Port8009
    languageja
  6. WebSocketノードとキャストノードをつなぎます。

  1. [デプロイ]ボタンでデプロイします。

動作を確認する

Herokuアプリケーションを開き、テキストボックスに喋らせたい内容を入力します。 [送信する]ボタンでクリックし、Google Homeが入力した内容を喋ってくれれば成功です。