Google Home/Nestを喋らせるgoogle-home-notifierの導入マニュアル [2020年12月版]
![Google Home/Nestを喋らせるgoogle-home-notifierの導入マニュアル [2020年12月版]](https://live.staticflickr.com/65535/50783393522_65fd874e32_q.jpg)
デバイスを喋らせるおもしろさ
今は「Google Nest」へシリーズ名を変えているGoogle製のホームデバイス「Google Home」を、コマンドで好きに喋らせる仕掛けの一つとして、Node.jsで動作する次の google-home-notifier がある。
ホームデバイスを喋らせる仕掛けをセットアップしておくと、部屋にいる家族に声で伝えたいことを外出中に喋らせたり、自宅サーバの起動完了を声で知ることができたりと、様々なことに応用できて便利だ。しかしながら、google-home-notifier本家の開発当時から数年が経過している関係で、現時点でこれを導入して動かすためには追加の手順が必要となっている。
そこで、今後の自分の為も含め、2020年12月時点でのgoogle-home-notifierの導入手順をまとめる。
ブログに残したい出来事
2020年12月の初め。google-home-notifierが利用するモジュールのひとつ、次のgoogle-tts-apiが、Error: get token key failed from google
というエラーでコケるようになり、その結果google-home-notifierも使えなくなった。
自力ではエラーを解決できず苦肉の策を考えていたところ、「とりあえず暫定対応してみました」と、いきなり、パッチ主の @freddiefujiwara さんからTwitterでメンションが。こんなありがたい、嬉しい出来事はブログ記事にして残すしかない。(感謝)
Google Homeにしゃべらせる仕掛けの、 google-home-notifier/node_modules/google-tts-api/lib/key.js が転ける。
— Masahiko OHKUBO (@mah_jp) December 1, 2020
| Error: get token key failed from google
この辺の議論だろうが、ねるます。
get key failed from google · Issue #33 · zlargon/google-tts · GitHub https://t.co/xuHvW9w7Co
とりあえず暫定対応してみました。今v0.0.6にアップデートされたみたいです。よろしければ試してみてください
— Fumikazu Fujiwara (@freddiefujiwara) December 6, 2020
うぉぉ、ありがとうございます。
— Masahiko OHKUBO (@mah_jp) December 6, 2020
google-tts-api 0.0.6 https://t.co/oY4x6hFXWh を利用することで、google-home-notifier がまた正常に使えることを確認しました。( https://t.co/81xMkt3dfm を内部で叩く改造が不要になりました)
google-home-notifier 導入マニュアル
導入環境
google-home-notifierの導入を試したPC環境は次の通り。
PC | IPアドレス | アーキテクチャ | OS |
---|---|---|---|
自宅サーバ1 (Raspberry Pi 4) | 192.168.1.2 | aarch64 | Ubuntu 20.04.1 LTS |
自宅サーバ2 (ECS LIVA) | - | x86_64 | Ubuntu 20.04.1 LTS |
Googleホームデバイスとして、サーバと同じLANに、Google-Home-Miniを2台、Chromecast-Audioを1台、無線接続している。各デバイスのIPアドレスはDHCPで割り当てている。以下の導入手順は、自宅サーバ1を対象にした例である。
導入手順
導入手順は、基本的には、google-home-notifier本家ページのListenerに記述されている内容で進める。ただし、サービスを外部公開するためのngrokの代わりに私は別の方法を使うことにして、ngrok関連の処理を外している。
$ git clone https://github.com/noelportugal/google-home-notifier
$ cd google-home-notifier
- package.jsonの「“google-tts-api”」行をこのように変更する
- (対象環境がラズパイの場合) 追加作業を行う→https://github.com/noelportugal/google-home-notifier#raspberry-pi
$ npm install
- browser.jsの一部をこのように変更する
- 付属のexample.jsの代わりに、自作server.jsを保存する
$ node server.js
- サーバ (192.168.1.2) の次のURLにアクセスする→http://192.168.1.2:8091/google-home-notifier?text=Hello+Google+Home
以上の、手順1~9を行うと、Google-Home-Miniが「Hello Google Home」と喋ってくれるはず。
手順3) package.jsonの変更箇所
google-tts-apiのバージョン指定を「0.0.6」に変える。google-tts-api本家ページのCHANGELOGにFix the change of Google Translate API
と記述されている版だ。
$ diff -up package.json.original package.json --- package.json.original 2020-12-31 17:50:35.286898269 +0900 +++ package.json 2020-12-31 17:51:16.306740064 +0900 @@ -17,7 +17,7 @@ "body-parser": "^1.15.2", "castv2-client": "^1.1.2", "express": "^4.14.0", - "google-tts-api": "0.0.2", + "google-tts-api": "0.0.6", "mdns": "^2.3.3", "ngrok": "^2.2.4" }
手順6) browser.jsの変更箇所
google-home-notifier本家ページのAfter “npm install”の記述内容に従う。
$ diff -up browser.js.original browser.js --- browser.js.original 1985-10-26 17:15:00.000000000 +0900 +++ browser.js 2020-12-31 17:56:17.874159294 +0900 @@ -118,7 +118,7 @@ Browser.create = function create(service Browser.defaultResolverSequence = [ rst.DNSServiceResolve() -, 'DNSServiceGetAddrInfo' in dns_sd ? rst.DNSServiceGetAddrInfo() : rst.getaddrinfo() +, 'DNSServiceGetAddrInfo' in dns_sd ? rst.DNSServiceGetAddrInfo() : rst.getaddrinfo({families:[4]}) , rst.makeAddressesUnique() ];
手順7) 自作server.jsの内容
example.jsを元に、次の変更を行っている。
- ngrok関連の処理をコメントアウト
- 喋らせたいホームデバイスの名前 (の前方の共通部分) を
deviceName
で指定し、IPアドレスでの指定は行わないので関連の処理をコメントアウト
$ cat server.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 deviceName = 'Google-Home-Mini'; // var ip = '192.168.1.20'; // default 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 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 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'); // ); })
自作server.jsのエラー問題が未解決
実は、上記の手順を経て起動した自作server.jsのサービスは、Google Homeを1度喋らせたあとにプロセスが意図せぬエラーで終了してしまう。その時の内容は次の通り。
server.js: エラー終了時のログ
$ node server.js *** WARNING *** The program 'node' uses the Apple Bonjour compatibility layer of Avahi. *** WARNING *** Please fix your application to use the native API of Avahi! *** WARNING *** For more information see <http://0pointer.de/blog/projects/avahi-compat.html> *** WARNING *** The program 'node' called 'DNSServiceRegister()' which is not supported (or only supported partially) in the Apple Bonjour compatibility layer of Avahi. *** WARNING *** Please fix your application to use the native API of Avahi! *** WARNING *** For more information see <http://0pointer.de/blog/projects/avahi-compat.html> { text: 'Hello Google Home' } Device "Google-Cast-Group-e8c73b83ff0b441a884936424a32536e" at 192.168.1.199:32161 Device "Google-Home-Mini-01a09e213c39b4d7151557adf3e31cf7" at 192.168.1.198:8009 timeout parameter is deprecated Device "Google-Home-Mini-e7dac5c24c8c4720c163b58859be6799" at 192.168.1.199:8009 timeout parameter is deprecated Device "Chromecast-Audio-42b0162ed37bd719776536eb4f73c9d5" at 192.168.1.197:8009 Device notified Device notified _http_outgoing.js:470 throw new ERR_HTTP_HEADERS_SENT('set'); ^ Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client at ServerResponse.setHeader (_http_outgoing.js:470:11) at ServerResponse.header (/home/foobar/tmp/google-home-notifier/node_modules/express/lib/response.js:771:10) at ServerResponse.contentType (/home/foobar/tmp/google-home-notifier/node_modules/express/lib/response.js:599:15) at ServerResponse.send (/home/foobar/tmp/google-home-notifier/node_modules/express/lib/response.js:145:14) at /home/foobar/tmp/google-home-notifier/server.js:86:15 at /home/foobar/tmp/google-home-notifier/google-home-notifier.js:35:11 at /home/foobar/tmp/google-home-notifier/google-home-notifier.js:70:7 at /home/foobar/tmp/google-home-notifier/google-home-notifier.js:95:9 at /home/foobar/tmp/google-home-notifier/node_modules/castv2-client/lib/controllers/media.js:81:5 at fn.onmessage (/home/foobar/tmp/google-home-notifier/node_modules/castv2-client/lib/controllers/request-response.js:27:7)
server.js: googlehomenotifier.serviceでサービス化
上記のエラーは、HTTPヘッダに関する何らかの不具合が起こっているためだろうと思うが、私がNode.jsを理解できておらず、どうやってエラーを解決すればいいか分からない。うーん。
しかし、落ちてしまうサービスを起動し直せば、google-home-notifierが再び利用できることは分かっているので、ものすごいバットノウハウなのであるが、文字通り「落ちたサービスを起動し直す」ようにした。
具体的には、次のサービスファイルgooglehomenotifier.service
を用意した上で、自作server.jsをsystemd配下のサービスとした。サービスファイルにRestartSec=5
と記述しているので、server.jsのサービスが停止して5秒するとサービスを再起動してくれる。
$ cat /etc/systemd/system/googlehomenotifier.service Description=google-home-notifier Server After=syslog.target network-online.target [Service] Type=simple User=root ExecStart=/usr/local/bin/node server.js Restart=on-failure RestartSec=5 KillMode=process WorkingDirectory=/home/foobar/tmp/google-home-notifier [Install] WantedBy=multi-user.target
P.S. ngrokの代替手段
自宅のgoogle-home-notifierをインターネットから使う、ngrok以外の手段として。
インターネットに公開されているサーバを1台用意して (以下VPSと表記)、次のような仕掛けを作ると、自宅サーバへインターネットから直接アクセスできない状態でも、https://<VPS>/google-home-notifier
へPOSTした内容を自宅のGoogle Homeが喋るようにできる。
- VPSの8091/tcp (localhost:8091) へ通信すると、その通信がgoogle-home-notifierを動かしている自宅サーバの8091/tcpへ届くように、自宅サーバにてautosshを用いた持続的なSSHポートフォワーディングを実行する (autosshをサービス化してSSH接続を強化 [2019-07-18] の応用)
- VPS上のnginxでリバースプロキシを設定し、localhost:8091をHTTPS (443/tcp) で公開する
そしてさらに、上記の公開URLhttps://<VPS>/google-home-notifier
へテキストをPOSTする入力フォームも用意すると、どこからでも、フォームへ入力した言葉を自宅のGoogle Homeが喋るというシステムができあがる。