ラズパイにgoogle-home-notifierを入れようとしたけどつまづいた


# google-home-notifierとは？
https://qiita.com/SatoTakumi/items/c9de7ff27e5b70508066
google homeから自発的に喋ってもらうことができます
自分はMacだとできたのですが、タイトルのようにラズパイに入れるのに躓きました

https://qiita.com/vanx2/items/3c20bf8e4111da9eb68d
google-home-notifier内のmdnsが外部モジュール依存だそうです
ここらへんのモジュールのインストールが失敗していました

# mdnsをピュアJSのmdns-jsに入れ替えるぞ！

ピュアjsなら環境に依存しないらしいぞ

## 今回入れた環境やバージョン
raspbian 8.0
node 9.3.0
npm 5.5.1

npmはできるだけ最新にしたいです
以下がnpm自体をアップデートするコマンドです

```
npm install -g npm
```

## gitからクローン

gitからクローンのみ
まだインストールしない

```
git clone https://github.com/noelportugal/google-home-notifier
cd google-home-notifier
```


## package.jsonの編集

vimにて編集する際のコマンド例

```
sudo vim package.json
```

21行目のmdnsをmdns-jsに変え、mdns-jsの最新のバージョンに変更
以下が編集後のファイル

``` package.json
{
  "name": "google-home-notifier",
  "version": "1.2.0",
  "description": "",
  "main": "google-home-notifier.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "Noel Portugal",
  "keywords": [
    "google home",
    "notifications",
    "notifier"
  ],
  "license": "MIT",
  "dependencies": {
    "body-parser": "^1.15.2",
    "castv2-client": "^1.1.2",
    "express": "^4.14.0",
    "google-tts-api": "https://github.com/darrencruse/google-tts/tarball/british-voice",
    "mdns-js": "^1.0.1",
    "ngrok": "^2.2.4"
  }
}
```

インストール開始

```
npm install
```


なぜかgoogle-tts-apiが見つからないとのエラーがでましたが、
npm updateでなぜか直りました

## google-home-notifier.jsの編集

変更した箇所はmdnsの宣言あたりと、browserを使った処理あたりです

mdnsのstopのタイミングだけ変わって、少し心配
でもとりあえず動いています
編集前のタイミングはいちおうコメントで残しています
mdnsとmdns-jsはだいたいデータが同じなのですが、nameじゃなくて、fullnameを見るようにしたので、差分だけ編集したい人はそこだけ注意です
play関数の方はまだ試せていないです〜

``` javascript:google-home-notifier.js
var Client = require('castv2-client').Client;
var DefaultMediaReceiver = require('castv2-client').DefaultMediaReceiver;
// var mdns = require('mdns');
// var browser = mdns.createBrowser(mdns.tcp('googlecast'));
var mdns = require('mdns-js');
var browser = mdns.createBrowser(mdns.tcp('googlecast'));

var deviceAddress;
var language;

var device = function(name, lang = 'en') {
    device = name;
    language = lang;
    return this;
};

var ip = function(ip) {
  deviceAddress = ip;
  return this;
}

var googletts = require('google-tts-api');
var googlettsaccent = 'us';
var accent = function(accent) {
  googlettsaccent = accent;
  return this;
}

var notify = function(message, callback) {
  if (!deviceAddress){
    browser.on('ready', function () {
      browser.discover();
    });
    browser.on('update', function(service) {
      console.log('Device "%s" at %s:%d', service.fullname, service.addresses[0], service.port);
      if (service.fullname != undefined && service.fullname.includes(device.replace(' ', '-'))){
        deviceAddress = service.addresses[0];
        getSpeechUrl(message, deviceAddress, function(res) {
          callback(res);
        });
        browser.stop();
      }
      //browser.stop();
    });
  }else {
    getSpeechUrl(message, deviceAddress, function(res) {
      callback(res);
    });
  }
};

var play = function(mp3_url, callback) {
  if (!deviceAddress){
    browser.on('ready', function () {
      browser.discover();
    });
    browser.on('update', function(service) {
      console.log('Device "%s" at %s:%d', service.fullname, service.addresses[0], service.port);
      if (service.fullname.includes(device.replace(' ', '-'))){
        deviceAddress = service.addresses[0];
        getPlayUrl(mp3_url, deviceAddress, function(res) {
          callback(res);
        });
        browser.stop();
      }
      // browser.stop();
    });
  }else {
    getPlayUrl(mp3_url, deviceAddress, function(res) {
      callback(res);
    });
  }
};

var getSpeechUrl = function(text, host, callback) {
  googletts(text, language, 1).then(function (url) {
    onDeviceUp(host, url, function(res){
      callback(res)
    });
  }).catch(function (err) {
    console.error(err.stack);
  });
};

var getPlayUrl = function(url, host, callback) {
    onDeviceUp(host, url, function(res){
      callback(res)
    });
};

var onDeviceUp = function(host, url, callback) {
  var client = new Client();
  client.connect(host, function() {
    client.launch(DefaultMediaReceiver, function(err, player) {

      var media = {
        contentId: url,
        contentType: 'audio/mp3',
        streamType: 'BUFFERED' // or LIVE
      };
      player.load(media, { autoplay: true }, function(err, status) {
        client.close();
        callback('Device notified');
      });
    });
  });

  client.on('error', function(err) {
    console.log('Error: %s', err.message);
    client.close();
    callback('error');
  });
};

exports.ip = ip;
exports.device = device;
exports.accent = accent;
exports.notify = notify;
exports.play = play;

```

## サンプルファイルの作成

これでいったん動作確認してみますか
同じフォルダ内にjsファイルを新規作成します

``` javascript:app.js
const googlehome = require('./google-home-notifier')
const language = 'ja';

googlehome.device('Google-Home', language); 

googlehome.notify('こんにちは。私はグーグルホームです。', function(res) {
  console.log(res);
});
```

そして実行

```
node app.js
```

どうでしょうか？話し始めましたか？

## example.jsの編集

https://qiita.com/azipinsyan/items/db4606aaa51426ac8dac
こちらのサイトのまんまです

こちらを実行するとURLが生成され、そのURLにGETやPOSTを送ると喋らせることができます
しかも同一LAN内じゃなくても大丈夫です

``` javascript: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 = 'ribihome';
var ip = '192.168.1.10'; // default IP      //ここにIPを記載


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

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

  if (!req.body) return res.sendStatus(400)
  console.log(req.body);

  var text = req.body.text;

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

  var language = 'ja'; // default language code　　　//ここに「ja」を記載
  if (req.query.language) {
    language;
  }

  googlehome.ip(ip, language);
  googlehome.device(deviceName,language)　　　　　　　//ここに命令文を追加

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

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

  console.log(req.query);

  var text = req.query.text;

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

  var language = 'ja'; // default language code　　　　　//ここに「ja」を記載
  if (req.query.language) {
    language;
  }

  googlehome.ip(ip, language);
  googlehome.device(deviceName,language)　　　　　　　　//ここに命令文を追加

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

app.listen(serverPort, function () {
  ngrok.connect(serverPort, function (err, url) {
    console.log('Endpoints:');
    console.log('    http://' + ip + ':' + serverPort + '/google-home-notifier');
    console.log('    ' + url + '/google-home-notifier');
    console.log('GET example:');
    console.log('curl -X GET ' + url + '/google-home-notifier?text=Hello+Google+Home');
        console.log('POST example:');
        console.log('curl -X POST -d "text=Hello Google Home" ' + url + '/google-home-notifier');
  });
})
```

# ついでにforever入れるぞ

一回起動すると放置ができない
sshで接続していようが、そのターミナルの画面と閉じるときにはそのプロセスも閉じることになるはず
なので、nodeをバックグランドで起動しておくようにする

https://qiita.com/setouchi/items/0dcc5869e7eb0ab524ea

```
sudo npm install -g forever
forever start example.js
```

起動してもコンソールがでないのでURLがわからない！
でも大丈夫

```
forever list
```

![スクリーンショット 2017-12-26 18.46.15.png](https://qiita-image-store.s3.amazonaws.com/0/76572/e394344d-98e2-8062-84fe-1e31b4b1dfc3.png "スクリーンショット 2017-12-26 18.46.15.png")

ここのlogfile（この場合は/home/pi/.forever/8Or1.log）にコンソールのログもあります
なので

```
cat /home/pi/.forever/8Or1.log
```

で確認
ちなみにforeverで立ち上げたプロセスを止めるのもこの画面のuidを確認後

```
forever stop 8Or1
```

# 追記(2018 1/18)
nodeのバージョンが古いとインストールできないとのこと
今回は当時の最新のv9.3だったはずだけれども、もしかしたらsudoのほうのnodeがデフォルトのほうのnodeになっていたのかもしれない
以下を参考に、nodeのアンインストールから最新をインストールが確実かもしれない
https://qiita.com/ktetsuo/items/8c9cd5714e231aa6ae09

その他に、google-home-notifierのgithubのreadmeに予めインストールしておいてねっていう記述が見られる
https://github.com/noelportugal/google-home-notifier

> Raspberry Pi

> If you are running from Raspberry Pi make sure you have the following before nunning "npm install": Use the latest nodejs dist.

> curl -sL https://deb.nodesource.com/setup_7.x | sudo -E bash -
> sudo apt-get install nodejs
> Also install these packages:

> sudo apt-get install git-core libnss-mdns libavahi-compat-libdnssd-dev

結構確認していたはずですが、色々抜けていました...
とりあえず、この記事はラズパイにgoogle-home-notifierを導入する一例ということで！