NodejsとGoogle Home Notifierのインストール & example.js

Nodejsのインストール


Nodejsのインストール手順をリスト1に示す。
# apt install curl npm -y
# npm install n -g
# n stable
# apt purge nodejs npm -y
リスト1.Nodejsのインストール

最初にaptを使ってnpmをインストールする(/usr/bin/nodejs, npm)。npmをインストールすると同時にnodejsもインストールされる。

次に、npmを使ってnをインストールする。
そして、nを使ってnodeとnpmをインストールする(これはaptでインストールしたものとは別:/usr/local/bin/node, npm)。

aptとnではインストールされるnodeやnpmのバージョンが異なる。aptでインストールしたnodeやnpmはバージョンが低いのでgoogle-home-notifierが使えない。
そこで、nでインストールしたnodeやnpmを使うこととする。

バージョン 格納場所
node 10.16.0 /usr/local/bin/node
npm 6.10.2 /usr/local/bin/npm

Google Home Notifier


Google Home Notifierアプリケーションの作成手順をリスト2に示す。
$ cd ~
$ mkdir pill-reminder
$ cd pill-reminder
$ npm init
$ npm install google-home-notifier
リスト2.Google Home Notifierアプリケーションの作成

Google Home Notifierアプリケーションはディレクトリを作成してその中に格納する。上記の例ではホームディレクトリにpill-reminderというディレクトリを作って、その中にプロジェクトを作成している。

npm initは package.jsonを作成する。
mtanaka@semi2014vpn:~/pill-reminder$ cat package.json
{
  "name": "pill-reminder",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "google-home-notifier": "^1.2.0"
  }
}
リスト3.package.json

google-home-notifierをインストールすると、カレントディレクトリ内にpackage-lock.jsonというファイルとnode_modulesというディレクトリが作成される。ディレクトリnode_modules内にはgoogle-home-notifiereと依存関係にあるモジュールが格納され、package-lock.jsonにそれらのバージョンや依存関係が記述される。

事後設定


Google Homeにプッシュ発話をさせる、Raspberry Pi 3へのgoogle-home-notifierの最新導入手順」によれば、google-tts-api(テキストを音声化するためにGoogle翻訳のTTS API)のバージョンアップが必要とのことである。カレントディレクトリ下の「node_modules/google-home-notifier/package.json」を以下のように修正する。
  ・・・
  "bundleDependencies": false,
  "dependencies": {
    "body-parser": "^1.15.2",
    "castv2-client": "^1.1.2",
    "express": "^4.14.0",
    "google-tts-api": "0.0.4",
    "mdns": "^2.3.3",
    "ngrok": "^2.2.4"
  },
  ・・・
リスト4.node_modules/google-home-notifier/package.jsonの修正

ここで、google-tts-apiのバージョンを"0.0.4"に設定している。この修正後、google-tts-apiをupdateする。
$ cd node_modules/google-home-notifier/
$ npm update google-tts-api
リスト5.google-tts-apiの更新

さらに、「node_modules/google-tts-api/lib/key.js」の修正も必要となるが、これについてはこのブログの Google home notifier のページを参照のこと。

その他、browser.jsの修正も必要らしいがこれを修正しなくとも動いているので放置した。何か問題が出てきたら検討することにする。

発話サーバ(example.js)


Google Home Notifierをインストールすると、カレントディレクトリの下の「node_modules/google-home-notifier」というディレクトリにexample.jsという発話サーバが格納されている。これは、HTTPリクエストに応えてGoogle Homeにメッセージを発話させるサーバプログラムである。

これを基にして以下のプログラムを作成した。
/**
 * 発話サーバ
 * node example.js or forever start example.js
 */
var express = require('express');
var googlehome = require('google-home-notifier');
var ngrok = require('ngrok');
var bodyParser = require('body-parser');
var app = express();
const serverPort = 8091; // default port

var deviceName = 'Google Home';
var ip = '192.168.10.254'; // default IP

var urlencodedParser = bodyParser.urlencoded({ extended: false });

app.post('/google-home-notifier', urlencodedParser, function (req, res) {
  
  if (!req.body) return res.sendStatus(400)
  putLog(JSON.stringify(req.body, null, ' '));
  
  var text = req.body.text;
  putLog('text=' + text);
  
  if (req.query.ip) {
     ip = req.query.ip;
  }

  var language = 'ja'; // default language code
  if (req.query.language) {
    language;
  }

  googlehome.device('Google Home', 'ja');
  googlehome.ip('192.168.10.116', 'ja'); 
  googlehome.accent('ja');

  if (text){
    try {
      if (text.startsWith('http')){
        var mp3_url = text;
        googlehome.play(mp3_url, function(notifyRes) {
          putLog(notifyRes);
          res.send(deviceName + ' will play sound from url: ' + mp3_url + '\n');
        });
      } else {
        googlehome.notify(text, function(notifyRes) {
          putLog('notifyRes:' + notifyRes);
          res.send(deviceName + ' will say: ' + text + '\n');
        });
      }
    } catch(err) {
      putLog(err);
      res.sendStatus(500);
      res.send(err);
    }
  }else{
    res.send('Please GET "text=Hello Google Home"');
  }
})

app.get('/google-home-notifier', function (req, res) {

  putLog(JSON.stringify(req.query, null, ' '));

  var text = req.query.text;

  if (req.query.ip) {
     ip = req.query.ip;
  }

  var language = 'ja'; // default language code
  if (req.query.language) {
    language;
  }
  googlehome.ip('192.168.10.116', 'ja');
  googlehome.device(deviceName,language);
  googlehome.accent('ja');
  if (text) {
    try {
      if (text.startsWith('http')){
        var mp3_url = text;
        googlehome.play(mp3_url, function(notifyRes) {
          putLog(notifyRes);
          res.send(deviceName + ' will play sound from url: ' + mp3_url + '\n');
        });
      } else {
        googlehome.notify(text, function(notifyRes) {
          putLog(notifyRes);
          res.send(deviceName + ' will say: ' + text + '\n');
        });
      }
    } catch(err) {
      putLog(err);
      res.sendStatus(500);
      res.send(err);
    }
  }else{
    res.send('Please GET "text=Hello+Google+Home"');
  }
})

/* 
 * 待ち受け
 * Port=8091をListhen
 * 同時にngrokのURLを取得して出力
 */
app.listen(serverPort, function () {
  putLog('Endpoints:');
  putLog('    http://' + ip + ':' + serverPort + '/google-home-notifier');
  
  connectNgrok(serverPort).then(url => {
    putLog('    ' + url + '/google-home-notifier');
    putLog('GET example:');
    putLog('curl -X GET ' + url + '/google-home-notifier?text=Hello+Google+Home');
    putLog('POST example:');
    putLog('curl -X POST -d "text=Hello Google Home" ' + url + '/google-home-notifier');
  });
})

/** 
 * ngrokを非同期で起動
 */
async function connectNgrok(port) {
    let url = await ngrok.connect(port);
    return url;
}

/**
 * ログの出力
 *
 */
function putLog(msg) {
 console.log(formatDate(new Date()) + ": " + msg);
}

/**
 * 日付をフォーマットする
 * @param  {Date}   date     日付
 * @param  {String} [format] フォーマット
 * @return {String}          フォーマット済み日付
 */
function formatDate(date, format) {
  if (!format) format = 'YYYY-MM-DD hh:mm:ss.SSS';
  format = format.replace(/YYYY/g, date.getFullYear());
  format = format.replace(/MM/g, ('0' + (date.getMonth() + 1)).slice(-2));
  format = format.replace(/DD/g, ('0' + date.getDate()).slice(-2));
  format = format.replace(/hh/g, ('0' + date.getHours()).slice(-2));
  format = format.replace(/mm/g, ('0' + date.getMinutes()).slice(-2));
  format = format.replace(/ss/g, ('0' + date.getSeconds()).slice(-2));
  if (format.match(/S/g)) {
    var milliSeconds = ('00' + date.getMilliseconds()).slice(-3);
    var length = format.match(/S/g).length;
    for (var i = 0; i < length; i++) format = format.replace(/S/, milliSeconds.substring(i, i + 1));
  }
  return format;
};
リスト6.発話サーバ

このプログラムはポート8091でHTTPリクエストのPOSTまたはGETメソッドを待ち受け、リクエストを受け取ったら受信データからメッセージを取り出し(textパラメタ)、それをGoogle Homeへ送信する。
また、ngrokを使ってインターネット側からのHTTPリクエストに対しても対応できるようになっている。

app.listenで待ち受け(Listen)を開始し、その後非同期関数connectNgrokでurlを取得し、それを使ってインターネット側から発話サーバへHTTPリクエストを送信するサンプルコマンドを表示している。

オリジナルのexample.jsではngrok.connect関数を非同期で呼び出していない。そのためか、この関数が応答なしとなってurlが返ってこない。調査すると、非同期型(async)の関数でラップするとうまくいくと書いてあったので、そのように修正したところurlが返ってきた。
2019-10-20 09:28:53.132: Endpoints:
2019-10-20 09:28:53.139:     http://192.168.10.254:8091/google-home-notifier
2019-10-20 09:28:54.245:     https://e8fcd8ef.ngrok.io/google-home-notifier
2019-10-20 09:28:54.245: GET example:
2019-10-20 09:28:54.245: curl -X GET https://e8fcd8ef.ngrok.io/google-home-notifier?text=Hello+Google+Home
2019-10-20 09:28:54.246: POST example:
2019-10-20 09:28:54.246: curl -X POST -d "text=Hello Google Home" https://e8fcd8ef.ngrok.io/google-home-notifier
リスト7.発話サーバの起動時のメッセージ(ngrokのurl)


0 件のコメント:

コメントを投稿