Mac
Node.js
OpenWeatherMap
天気予報
GoogleHome

気象情報API(OpenWeatherMap)を使用して通勤時間帯の天候不良を判定し、音声案内させる

OpenWeatherMapの気象情報を使い、指定時間帯の天気が「晴れ」もしくは「曇り」でない場合に、Macの日本語音声もしくはGoogle Homeで音声案内するアプリケーションを作成しました。アプリケーションはMacで実行し、crontabで指定日時に自動実行させることもできます。

はじめに

東京は雨や雪になると通勤電車がいつも以上に混雑してしまうので、起床したタイミングで 気象情報を自動的に確認し、混雑時間帯を避けて早めに家を出る(もしくは遅めに家を出る)ための判断を、音声案内で支援するアプリケーションを作成してみました。

アプリケーションはNode.jsで実装しています。気象情報はOpenWeatherMapから取得しています。音声案内は、Macのsayコマンドを使用し、日本語Siriと同じkyokoの声でしゃべらせる方法と、Google Homeにしゃべらせる方法の2つをこのエントリーでは書いています。音声案内はその場にいないと聞き漏らしてしまう問題があるので、Slackにもメッセージを残すようにしてみました。用途に応じて不要な機能の箇所はコメントアウトしてください。

アプリケーションは手動実行する方法でもいいのですが、忘れずにチェックしてくれることに意味があると思いますので、Macのcrontabで平日午前7時に自動実行する方法も紹介します。

作成したもの

下記ツイートの動画のように、指定日時(無指定ではアプリケーション実行時刻から見て「次の午前9時」時点)の天候を確認し、天候が「晴れ」もしくは「曇り」でない場合は、MacとGoogle Homeで指定した言葉を喋らせることができます。ちなみにこの動画撮影時点での翌日午前9時の天候の予報は「雨」だったので音声案内がされました(「晴れ」もしくは「曇り」ではなんの音もしません)。

セットアップ

OpenWeatherMap

OpenWeatherMapのAPIにアクセスするためには、サインアップしてフリーのアカウントを作成する必要があります。アカウント作成後、サインインするとAPI KeysページでAPIキーが取得できますので、これをコピーしておいてください。

Node.js

クライアント上でNode.jsを使用しますので、インストールをしていない方はNode.jsをインストールしてください。Node.jsのインストール方法はこのエントリーでは省略します。後述するNode.jsのアプリケーションでは下記のモジュールを使用しますので、必要なものはインストールしておいてください。

Nodeモジュール
$ npm install openweathermap-node
$ npm install moment
$ npm install google-home-notifier
$ npm install @slack/client

Slack

このエントリーではSlackのIncoming Webhooksを使用して、Slackの指定したチャンネルにメッセージを書き込みます。SlackのIncoming Webhooks連携の詳細な手順は、他の方のQiitaのエントリーですでに解説されていますので省略しますが、incoming webhook integrationにアクセスし、通知させたいチャンネルを指定し、「Webhook URL」を取得したらコピーしておいてください。

Node.jsアプリケーション

Node.jsのアプリケーションは下記のファイルのみです。アプリケーション実行時の引数で判定時間を取得し、指定がなければ午前9時を使用します。なお、OpenWeatherMapの天気予報が午前00時00分から3時間ごとのデータのみのため、入力できる値は時間(HH)の2桁数字だけです。

アプリケーション実行時刻と天気予報の判定時刻(仮に午前9時00分とする)を比較し、実行時刻が判定時刻よりも前(午前0時〜午前8時59分)であればその日の判定時刻の天気、実行時刻が判定時刻以降(午前9時00分〜23時59分)であれば翌日の判定時刻の天気を確認します。

OpenWeatherMapの「5 day / 3 hour forecast」のAPIにアクセスし、取得したJSONの中から判定時刻のものを探し、その時刻の天候が「晴れ(Clear)」もしくは「曇り(Clouds)」でない場合に、音声案内とSlack通知を実行します。

index.js
// OpenWeatherMap
const OpenWeatherMapHelper = require("openweathermap-node");
const helper = new OpenWeatherMapHelper(
    {
        APPID: 'xxxxxxxxxxx', // OpenWeatherMapのAPI keyを指定する
        units: "metric"
    }
);

const location = "Tokyo";

// 第2引数が指定されていなければ「09」を使用
var check_hh = process.argv[2] || "09"; // 00, 03, 06, 09, 12, 15, 18, 21
switch (check_hh) {
    case "00":
    case "03":
    case "06":
    case "09":
    case "12":
    case "15":
    case "18":
    case "21":
        // 入力値に問題なし
        break;
    default:
        console.log("[ERROR] 入力値が不正です。00,03,06,12,15,18,21のいずれかから入力してください。");
        console.log("[INFO] 特に指定がない場合は09が選択されます。");
        process.exit(1);
}

// チェック対象日時取得
const moment = require("moment");
var today = moment().format("YYYY-MM-DD " + check_hh + ":00:00");
var tomorrow = moment().add(1,"days").format("YYYY-MM-DD " + check_hh + ":00:00");
var num_now_hh = Number(moment().format("HH"));
var num_target_hh = Number(check_hh);

// チェック対象時刻を過ぎている場合は翌日を指定
if (num_now_hh < num_target_hh) {
    check_target = today;
} else {
    check_target = tomorrow;
}

helper.getThreeHourForecastByCityName(location, (err, threeHourForecast) => {
    if(err){
        console.log(err);
    }
    else{
        for (var i = 0; i < 18; i++) {
            var tmpForecast = threeHourForecast["list"][i]["dt_txt"];
            if (tmpForecast == check_target) {
                // 天候はThunderstorm, Drizzle, Rain, Snow, Atmosphere, Clear, Cloudsのいずれか
                // https://openweathermap.org/weather-conditions
                switch (threeHourForecast["list"][i]["weather"][0]["main"]) {
                    // 天候がClear, Cloudsのいずれかであればアクションなし
                    case "Clear":
                    case "Clouds":
                        break;
                    // 天候がRain, Snow, Thuderstorm, Drizzle, Atmosphereのいずれかであればアクションあり
                    case "Rain":
                    case "Snow":
                    case "Thunderstorm":
                    case "Drizzle":
                    case "Atmosphere":
                        // Sayコマンドで日本語Siri(kyoko)の声で音声案内
                        const execSync = require('child_process').execSync;
                        var result = execSync('say -v kyoko "こんにちは。天気予報のお知らせです。天候不良が予想されています。必要な準備をしてください。繰り返します。天候不良が予想されています。必要な準備をしてください。以上で天気予報のお知らせを終了します。"');

                        // Google Homeで音声案内
                        var googlehome = require('google-home-notifier');
                        var language = 'ja';
                        googlehome.device('Google Home', language);
                        googlehome.notify('こんにちは。天気予報のお知らせです。天候不良が予想されています。必要な準備をしてください。繰り返します。天候不良が予想されています。必要な準備をしてください。以上で天気予報のお知らせを終了します。', function(res) {
                        console.log(res);
                        });

                        // Slack通知
                        const { IncomingWebhook } = require('@slack/client');
                        const url = "xxxxxxxxxxxx"; // SlackのWebHook URLを指定する
                        const webhook = new IncomingWebhook(url);
                        // Send simple text to the webhook channel
                        webhook.send('天候不良が予想されています。必要な準備をしてください。', function(err, res) {
                            if (err) {
                                console.log('Error:', err);
                            } else {
                                console.log('Message sent: ', res);
                            }
                        });
                        break;
                    default:
                        console("[ERROR] 想定外の結果:" + threeHourForecast["list"][i]["weather"][0]["main"]);
                        break;
                }
            }
        }
    }
});

5行目のAPPIDにはOpenWeatherMapのAPI keyを、80行目のurlにはSlackのWebhook URLを指定してください。

10行目で天気予報の対象都市を「Tokyo」にしています。他の都市を対象とする場合は、この値を変更してください。その都市の気象情報が提供されているかはOpenWeatherMapで都市名を検索してあらかじめ確認しておいてください。

68行目がMacに、74行目がGoogle Homeにしゃべらせる言葉で、83行目がSlackに書き込む言葉です。言葉は好きな言葉に変更してください。

下記のようなコマンドで、アプリケーションを実行します。引数を指定しないと午前9時の天気予報を基に判定します(13行目参照)。引数に例えば「18」を指定すると、午後6時(18時)の天気予報を基に判定します。

アプリケーション実行
$ node index.js
$ node index.js 18

指定時刻での自動実行

Macの場合はcrontabが使用できますので、crontabで平日の午前7時にこのアプリケーションを実行させます。

crontab設定に必要な情報のため、nodeコマンドのパスをwhichコマンドで確認します。続いて、上記のNode.jsアプリケーション(index.js)を配置したパスを確認します(後者は仮に「/Users/YourName/Documents/OpenWeatherMap/index.js」とします)。crontabの書き方というページも参考に、自分の都合の良い日時を指定してください。下記設定例は月曜日から金曜日まで07時00分にアプリケーションを実行すると記述しています。

nodeのパス確認し、crontabを編集
$ which node
/usr/local/bin/node
$ crontab -e
crontab設定例
00 07 * * 1-5 /usr/local/bin/node /Users/YourName/Documents/OpenWeatherMap/index.js

なお、crontabはMacがシャットダウンされていたり、スリープしたりしていると実行されないようです。シャットダウンはともかくスリープは早朝自動実行では問題になりそうですので、【Mac】cronがスリープ状態だと動かないを参考にcrontabの実行時刻の少し前にスリープ解除をしておくことも必要かもしれません。

おわりに

OpenWeatherMapのAPIが無料で使えることがわかったので、簡単な音声案内アプリケーションを作成してみました。ただし、このアプリケーションの一番の問題はOpenWeatherMapの天気予報の精度だと思います。日本気象協会のtenki.jpやウェザーニューズと比べても独自の予報をする傾向があると私は感じています。その点でこの音声案内も現状では参考意見程度に留めるべきだと思います。