16
19

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

GoogleHome に電車遅延情報を喋ってもらう(google-home-notifier編)

Last updated at Posted at 2018-11-20

#はじめに

image.png

朝の電車遅延は嫌ですよね。遅れてるいるものはどうにもならないので、せめて駅に着く前に心の準備をしておきたいものです。

朝、決まった時刻に列車運行情報をスクレイピングして遅延していたらGoogleHomeにしゃべってもらいます。

#構成
image.png

RaspberryPiで定刻になったらYahoo路線情報ページをスクレイピングして正常運行以外ならば google-home-notifier を使ってGoogleHomeにしゃべってもらいます。google-home-notifier のしゃべりがイマイチなので、音声は VoiceText に変更します。

#ハード

  • GoogleHome (私のはminiです)
  • RaspberryPi (今回は3B)
  • 母艦(Windowsマシン。何でも良い)

#ソフト

  • raspbian (今回は 2017-09-07-raspbian-stretch を使いました)
  • nodejs (v9.8.0)
  • npm (v6.4.1)
  • google-home-notifier
  • VoiceText

中心となるソフトウェアは上記です。
raspbian、nodejs、npm は設定済み前提です。

#google-home-notifier のインストール
まず始めに作業ディレクトリ(trainfo)を作成しておき、そこで作業します。

$ mkdir ~/trainfo
$ cd ~/trainfo
$ npm init

google-home-notifier インストールの前に必要なライブラリを入れてから。

$ sudo apt-get install libavahi-compat-libdnssd-dev
  :
$ npm install google-home-notifier

最近、google-tts-api のキーが変更したらくこのままでエラーになって動きません。
下記のファイルを修正する。24行目辺り。

~/trainfo/node_modules/google-tts-api/lib/key.js
      //eval(html.match(/TKK=eval\(\'\(.*\)\'\);/g)[0]);  // TKK = '405291.1334555331'
      eval(html.match(/TKK='[0-9]+.[0-9]+'/g)[0]);

#google-home-notifier のテスト
以下のファイルを作成する。
'ダイニング ルーム' は、GoogleHomeの名前ですが、IPアドレスを指定する場合は多分何でも良いはずです。'ja' を渡したいだけのコード。

GoogleHomeの名前とGoogleHomeのIPアドレスはGoogleHomeアプリの設定にあります。

~/trainfo/notifier.js
const googlehome = require('google-home-notifier')

googlehome.ip("192.168.0.200"); // ここはGoogleHomeのIPアドレス
googlehome.device('ダイニング ルーム', 'ja');  // ここは GoogleHome の名前

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

実行

$ node notifier.js

♪こーんーにーちーはーわーたーしー・・・・♪
と間延びした声が聞こえてくればテスト完了です。
これでも良いのですが、VoiceText を使えば更に良くなります。

#VoiceText Web API
VoiceTextとはなんぞやというと、文字列を合成音に変えてくれるTTS(TextToSpeech)エンジンです。google-home-notifier では、google-tts-api というやつが TTSエンジンとして使われてます。で、google-tts-api を使わずに VoiceText に置き換えようと。

VoiceText は無料ですがAPIキーが必要です。取得しましょう。
VoiceText Web API
メールアドレスだけで取得できます。

#VoiceText のインストール

$ npm install voicetext

#google-home-notifier を修正

google-tts-api ではなく VoiceText を使うように、google-home-notifier を修正する。変更箇所には [nori-dev] が付けてある。

~/trainfo/node_modules/google-home-notifier/google-home-notifier.js

// 6行目あたり
var language = 'ja';  // add ='ja' [nori-dev]

// add start [nori-dev]-->
var VoiceTextWriter = require('./VoiceTextWriter');
var voiceTextWriter = new VoiceTextWriter();
// <-- add end [nori-dev]

var device = function(name, lang = 'ja') {
:

// 71行目あたり
var getSpeechUrl = function(text, host, callback) {
  /* delete start [nori-dev]-->
  googletts(text, language, 1).then(function (url) {
    onDeviceUp(host, url, function(res){
      callback(res)
    });
  }).catch(function (err) {
    console.error(err.stack);
  });
  <-- delete end [nori-dev]*/
  // add start [nori-dev]-->
  voiceTextWriter.convertToText(text).then(function(result, reject){
    onDeviceUp(host, result, function(res){
     callback(res)
   });
  }).catch(function onRejected(error){
   console.error(error);
  });
  // <-- add end [nori-dev]
};
:

// 98行目あたり
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
      };
      // add start [nori-dev]-->
      if(url.endsWith('wav')){
        media.contentType = 'audio/wav';
      }else if(url.endsWith('ogg')){
        media.contentType = 'audio/ogg';
      }
      // <-- add end [nori-dev]
      player.load(media, { autoplay: true }, function(err, status) {
        client.close();
        callback('Device notified');
      });
    });
  });

#VoiceTextWriter.js の作成
VoiceTextWebAPI を呼び出すところを作成します。google-home-notifier.js と同じところに保存してください。
ソース中の <VoiceTextのAPIキー> と <RaspberryPIのIP>には適宜入力。
OUT_PATH は VoiceTextWebAPI で作成されたファイルを置く場所です。
次項の api.js の内容と合わせてください。

$mkdir ~/trainfo/voice
~/trainfo/node_modules/google-home-notifier/VoiceTextWriter.js
var fs = require('fs');
var VoiceText = require('voicetext');
var voice = new VoiceText('<VoiceTextのAPIキー>');
var OUT_PATH = '/home/pi/trainfo/voice/_temp.wav'
var OUTPUT_URL = 'http://<RaspberryPIのIP>:8080/googlehome/_temp.wav';

class VoiceTextWriter {

    convertToText(text) {
        return new Promise(function (resolve, reject) {
            voice
                .speaker(voice.SPEAKER.HIKARI)
                .emotion(voice.EMOTION.HAPPINESS)
                .emotion_level(voice.EMOTION_LEVEL.HIGH)
                .volume(150)
                .speak(text, function (e, buf) {
                    if (e) {
                        console.error(e);
                        reject(e);

                    } else {
                        fs.writeFileSync(OUT_PATH, buf, 'binary');
                        resolve(OUTPUT_URL);
                    }
                });
        });
    }
}
module.exports = VoiceTextWriter;

api.js の作成

GoogleHomeからのHTTP要求に応答するHTTPサーバを express で作ってしまいます。
必要なライブラリをインストール。

$ npm install express body-parser fs
~/trainfo/api.js
const express = require('express');
const bodyParser = require('body-parser');
const fs = require('fs');

const app = express();
const serverPort = 8080;
const urlencodedParser = bodyParser.urlencoded({ extended: false });

// CORSを許可する
app.use(function(req, res, next) {
  res.header("Access-Control-Allow-Origin", "*");
  res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
  next();
});

/**
 * 音声ファイルを取得する
 */
app.get('/googlehome/:voiceFileName', (req, res) => {
  const voiceFileName = req.params.voiceFileName;
  if (!voiceFileName) {
    res.status(400).send('Invalid Parameters.');
}

  const file = fs.readFileSync('/home/pi/trainfo/voice/' + voiceFileName, 'binary');
  res.setHeader('Content-Length', file.length);
  res.write(file, 'binary');
  res.end();
});

app.listen(serverPort, () => {
  console.log(`Start api-server. Port is ${serverPort}`);
})

api.js は常に実行させておく必要があるので forever します。
まだインストールしていない場合は

$ sudo npm install -g forever
  :
$ forever start api.js

これでVoiceText準備はOK。

#VoiceTextのお試し
先程実行した notifier.js を再度。

$ node notifier.js

いかがですか?いい声出ましたか?
次はいよいよ運行情報の取得です。

#Yahoo路線情報ページをスクレイピング

先程使用した notifier.js を拡張します。スクレイピングには cheerio-httpcli を使いますのでインストールします。

$ npm install cheerio-httpcli

下記のソースでは、3路線の情報を取得しています。適宜変更して使ってください。
「平常運転」のときは何もしゃべりませんので、まずは遅延があるところでテストしてみてください。

Yahoo路線情報(https://transit.yahoo.co.jp/traininfo/top)

~/trainfo/trainfo.js
const googlehome = require('google-home-notifier')
const client = require('cheerio-httpcli');

googlehome.ip("<GoogleHomeのIP>");
googlehome.device('ダイニング ルーム', 'ja');

const voice = [];

client.fetch('https://transit.yahoo.co.jp/traininfo/detail/22/0/') // 京浜東北線
    .then(function (result) {
        check(result);
        return client.fetch('https://transit.yahoo.co.jp/traininfo/detail/29/0/'); // 横須賀線
    })
    .then(function (result) {
        check(result);
        return client.fetch('https://transit.yahoo.co.jp/traininfo/detail/125/0/'); // 相鉄本線
    })
    .then(function (result) {
        check(result);
    })
    .catch(function (err) {
        console.log(err);
    })
    .finally(function () {
        var text = voice.join();
        if (text.length > 0) {
            googlehome.notify(text, function (res) {
                console.log(res);
            });
        }
    });

function check(result) {
    if (!result.error) {
        var $ = result.$;
        var ti = $('h1.title').text().trim();
        var sv = $('#mdServiceStatus > dl > dt').text().trim();
        var tr = $('#mdServiceStatus > dl > dd.trouble').text().trim();
        if (!sv.match('平常運転')) {
            voice.push(ti + tr);
        }
        console.log(ti);
        console.log(sv);
        console.log(tr);
    }
}

#実行

pi@raspi01:~/train $ node trainfo.js
*** WARNING *** The program 'node' uses the Apple Bonjour compatibility layer of Avahi.
  :
白新線
[!]列車遅延
19:26頃、羽越本線内で線路内点検を行った影響で、現在も列車に遅れが出ています。(11月20日 21時45分掲載)
横須賀線
[○]平常運転

相鉄本線
[○]平常運転

Device notified

WARNING は見なかったことにしてちゃんと音声が出ましたでしょうか?
ここまでくればあともう少し!
※「平常運転」のときは、なにもしゃべりません。

#cron
朝にしゃべってもらわないといけないのでスケジュールします。

$ crontab -l
#平日7:00から15分ごとに運行情報プログラムを実行する
0,15,30 7 * * 1-5 node /home/pi/trainfo/trainfo.js

それと前述した api.js を システム起動時に実行されるように /etc/rc.local にでも入れておいてください。

/etc/rc.local
  :
# google-home-notifier voicetext 運行情報で使う
forever start /home/pi/trainfo/api.js

exit 0

以上です。

#ソース
今回使用した作成・修正のファイルを以下においておきます。
github:https://github.com/nori-dev-akg/trainfo

#SpecialThanks
https://qiita.com/zono_0/items/95418d9ceaad3836a758
https://qiita.com/ktetsuo/items/8c9cd5714e231aa6ae09
http://gadgetneko.blog.jp/archives/5326857.html

16
19
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
16
19

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?