自分が本当に知りたかった天気情報を教えてくれるサービスをAlexaスキルでつくる

この記事は、[mybest Advent Calendar 2018](https://adventar.org/calendars/3397)の３日目です。

こんにちは。普段はサーバーサイドエンジニアとして働いている @miraoto です。


突然ですが、皆さんはルーティンワークとなっている朝のタスクはありますか？
「起きる」「顔を洗う」「朝食を食べる」「朝ごはんを食べる」など、毎朝何気なくやっている行動には色々あると思いますが、その中でもふと自分の「今日の天気を調べる」という行動に疑問を持ちました。天気予報をみる時って実は細かい情報が知りたい訳ではないですよね。

自分の場合、天気予報を調べる目的は２つです（実際は他にもありそうですがとりあえずルーティンで見そうなやつだけ取り上げました）

1. 昨日の（寒い｜暖かい）日と比較して、今日は（寒い｜暖かい｜同じくらい）のか？
2. 今日（出社｜退社）する時間に傘は必要か？

つまり、天気の細かい情報が知りたいわけではなく、この辺りの情報さえ知ることができれば、大体の目的は達成されそうで、それを自動で判断して通知してくれる仕組みを作れば朝（ほんのちょっとだけ）考えることが減りそうです。

いい機会なので、今回はこのmybestな天気予報サービスを、仕組みをAlexaスキルを用いて実装してみました。


# Alexaスキルの作成

今回のメインとなるAlexaスキルの登録〜作成です。

## Amazon developerに登録。Alexaスキルを作成ページに進む

まずは、[Amazon developer](https://developer.amazon.com/ja/)に登録します。
その後、[Alexa Developer Console](https://developer.amazon.com/alexa/console/ask)から「スキルの作成」に進みます

![Alexa Developer Console (1).png](https://qiita-image-store.s3.amazonaws.com/0/100018/8ca7f684-bd81-a3e6-4076-1f6b9ef87f53.png)

## 呼び出し名を決める

Alexaスキルは、 「``` [呼び出し名] ``` を開いて、 ``` [インテント] ```」みたいな感じでコミュニケーションをとるらしく、初めの読み出し名を決めます。呼び出し名にはいくつかの要件があるので、それを満たすように設定します。（インテントについては後述）

![image.png](https://qiita-image-store.s3.amazonaws.com/0/100018/b7e33120-0582-dc3f-5532-bf5c15c9fdad.png)


## インテントを作成する
インテントはAlexaに解釈してほしい言葉と、実際のアクションを繋ぐようなものらしいです。
インテントは標準で用意されているビルトインインテントと独自で作成することのできるインテントがあります。
今回は、用途に合わせて独自のインテントを用意してみました。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/100018/c5fe4362-88f5-9f00-ae54-168179b6d008.png)

設定はここまでです。簡単ですね。

## モデルをビルドする

最後に「モデルをビルド」みたいなボタンを押して、今作成したAlexaスキルをビルドします。

## エンドポイントを登録する
Alexaからのリクエストを受けて、天気予報の情報を返してくれるエンドポイントを登録します。
ホスティング方法は「AWS LambdaのARN（推奨）」と「HTTPS」の２パターンありますが、今回は、最近あまり触っていないという理由だけでheroku（node.js） で用意してみました。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/100018/dce4001e-c31c-8724-39cc-a912891cfc88.png)


エンドポイントの先の実装は後述。取り急ぎリクエストを送ったら一定のレスポンスが返ってくるところまで用意しておきます。
これでAlexaスキルをとりあえず作成することができたので、テストしてみます。

## Alexaスキルをテストする
テストはalexa developer consoleのGUI上で行えます（デバッグでAlexaに都度話さなくていいのは楽ですね）

![Alexa Developer Console (6).png](https://qiita-image-store.s3.amazonaws.com/0/100018/c18ec0ed-1b9d-8ea4-3d23-3b433bbc3ab9.png)

疎通が確認できましたので、Alexa スキル側の設定はここまでです。


# エンドポイントの作成

先ほどさらっと済ませてしまっていたエンドポイントの要件を決めます。もう一度最初の目的を思い出してみます。

1. 昨日の（寒い｜暖かい）日と比較して、今日は（寒い｜暖かい｜同じくらい）のか？
2. 今日（出社｜退社）する時間に傘は必要か？

今回やりたかったことは2つあったんですが、どうやらこのペースで進めるとアドベントカレンダーの公開までに間に合いそうにない感じがしたので、今回は1.の方だけ対応してみることにしました。

天気情報の取得には、無料かつ過去の天気情報にもアクセスできるという理由で、[OpenWeatherMap](https://openweathermap.org/api)の「Current weather data」と「Historical data」というAPIを利用してみます。

実際に返すメッセージの条件はこんな感じです

 * 前日比の差分で、±２度の場合は、「今日は昨日よりもちょっと（寒い｜暑い）です」とする
 * 前日比の差分で、±5度の場合は、「今日は昨日よりもだいぶ（寒い｜暑い）です」とする

この辺りを実装するとこんな感じになりました（一部省略。雑なのはご了承ください..

```javascript

const request = require('request');
const syncRequest = require('sync-request');
const express = require('express');
const bodyParser = require('body-parser');

const app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));

const server = app.listen(process.env.PORT || 5000, () => {
  console.log('Express server listening on port %d in %s mode', server.address().port, app.settings.env);
});

// for debug
app.get('/', (req, res) => {
  handleResponse(res);
});

app.post('/', (req, res) => {
  handleResponse(res);
});


function handleResponse(res) {

  const tempDiff = getTemperatureDifference();

  const data = {
    version: "1.0.0",
    response: {
      outputSpeech: {
        type: 'PlainText',
        text: getMessage(tempDiff),
        ssml: '<speak>SSML text string to speak</speak>'
      }
    }
  };
  res.json(data);
}


function getTemperatureDifference() {
  let date = new Date();
  const today = date.getTime();
  const todayUrl = 'http://api.openweathermap.org/data/2.5/weather?id=1850147&type=hour' +
    `&appid=${process.env.API_KEY}`

  date.setDate(date.getDate() - 1);
  const yesterday = date.getTime();
  const yesterdayUrl = 'http://history.openweathermap.org/data/2.5/history/city?id=1850147&type=hour' +
    `&appid=${process.env.API_KEY}` +
    `&start=${today}` +
    `&end=${yesterday}`

  // 一部省略...

  const todayTemp = /* APIの返り値を加工　*/;
  const yesterdayTemp = /* APIの返り値を加工　*/;

  return todayTemp - yesterdayTemp;
}

function getMessage(tempDiff) {
  if (tempDiff >= 5 ) {
    return '今日は昨日よりもだいぶ暑いですね'
  }
  else if (tempDiff >= 2 ) {
    return '今日は昨日よりもちょっと暑いですね'
  }
  else if (tempDiff <= -2 ) {
    return '今日は昨日よりもちょっと寒いですね'
  }
  else if (tempDiff <= -5 ) {
    return '今日は昨日よりもだいぶ寒いですね'
  }
  else {
    return '昨日と同じぐらいですね'
  }
}
```

# 使ってみる
結果がテストの文言とほぼ同じなのでわかりずらいですが、ちゃんと気温を比較してなんとなくの温度感を伝えてくれるようになりました　:)

![image.png](https://qiita-image-store.s3.amazonaws.com/0/100018/31643d41-931f-17d4-794a-57236a810c32.png)



# 感想
一番時間がかかったのは用途にあった天気予報APIを探すところで、Alexaスキルの開発はとても簡単にできました。
今回の天気のような、実際に意思決定に必要な情報を無意識に解釈しているシーンは他にもありそうなので、身の回りのそういった行動を全部Alexaスキルのような音声コミュニケーションに置き換えられたら近未来感がある生活ができそうです。
あと、細かいところをこだわって作りたいなら前日ではなくもっと前もって動かなくてはいけないですね。

ということで、みなさま楽しいAlexaライフをお過ごしください〜