Help us understand the problem. What is going on with this article?

よく風邪を引くのでちょうどいい布団を教えてくれるLINE BOT作った

風邪引きたくない

最近すっかり秋になり、気温の変化が激しいですね。
僕はまだタオルケットで寝ているのですが、朝起きると凍えるような寒さでよく風邪を引いてしまいます。

今の時期はどんな布団で寝るといいのかググってみると、東京西川さんのホームページに情報が載っていました。
室温が20℃前後の時は、真綿掛布団をかぶって寝るとちょうど良いそうです。

せっかくなので通年で使えるよう、東京の夜~明け方の気温を取得し、ちょうどいい布団を教えてくれるLINE BOTを作りたいと思います。

使用イメージ

593153202.580649.gif

システム構成図

システム構成は以下の通り。

architecture.png

作り方

LINE BOT

主に下記の記事を参考にさせてもらいました。
- https://qiita.com/n0bisuke/items/ceaa09ef8898bee8369d
- https://dev.classmethod.jp/etc/lambda-line-bot-tutorial/

ソースコードの書き換え(2019/10/21更新)

Lambda関数のソースは以下のようになっています。
これを$ tscでコンパイルすると動くはず。

環境変数は以下の3つをLambdaのコンソールで追加してください。

  • LINE_CHANNEL_SECRET:BOTのチャネルシークレット(LINE Developersコンソールから取得)
  • LINE_CHANNEL_ACCESS_TOKEN:BOTのアクセストークン(LINE Developersコンソールから取得)
  • WEATHER_API_KEY:OpenWeatherAPIのキー(OpenWeatherMapログイン後に取得)
const line = require('@line/bot-sdk');
const client = new line.Client({
  channelSecret: process.env.LINE_CHANNEL_SECRET,
  channelAccessToken: process.env.LINE_CHANNEL_ACCESS_TOKEN,
});
const axios = require('axios');
const dayjs = require('dayjs');

const weatherApiConfig = {
  key: process.env.WEATHER_API_KEY,
};

interface PeriodInterface {
  begin: string;
  end: string;
}

interface WeatherInfoInterface {
  min: number;
  max: number;
  humidity: number;
}

interface BedclothesInterface {
  best: string;
}

exports.handler = async (event: any) => {
  const body = JSON.parse(event.body);
  console.log('event', JSON.stringify(event));
  console.log('body', JSON.stringify(body));

  if (body.events[0].replyToken === '00000000000000000000000000000000' && body.events[1].replyToken === 'ffffffffffffffffffffffffffffffff') {
    console.log('Developerコンソールからの疎通確認');
    return {
      statusCode: 200,
    };
  }

  try {
    const result = await Promise.all(body.events.map(handleEvent));
    console.log('result', JSON.stringify(result));

    return {
      statusCode: 200,
    };
  } catch (e) {
    console.error(JSON.stringify(e));

    return {
      statusCode: 500,
    };
  }
};

const handleEvent = async (event: any) => {
  if (event.type !== 'message' || event.message.type !== 'text') {
    return Promise.resolve(null);
  }

  const weathers = await axios.get(`${process.env.WEATHER_API_BASE_URL}/forecast?q=tokyo&units=metric&lang=ja&APPID=${weatherApiConfig.key}`);
  console.log(weathers.data.list);

  const { begin, end } = getPeriod();
  console.log({ begin, end });

  // 取得した天気リストの中から、夜9時~翌6時の情報のみに絞る
  const nightWeathers = weathers.data.list.filter((weather: any) => {
    return begin < weather.dt && weather.dt < end;
  });
  console.log({ nightWeathers: JSON.stringify(nightWeathers) });

  const weather = selectOne(nightWeathers);
  console.log(weather);

  const bestBedclothes = getBestBedClothes(weather);
  console.log({ bestBedclothes });

  return client.replyMessage(event.replyToken, {
    type: 'text',
    text: `今夜は、${bestBedclothes.best}がおすすめです。\n\n睡眠気温\n最高気温:${Math.round(weather.max * 10) / 10}℃\n最低気温:${Math.round(weather.min * 10) / 10}℃\n湿度  :${weather.humidity}%`,
  });
};

// 9時~翌6時のデータを取得するための、UNIX時間を計算する
const getPeriod = (): PeriodInterface => {
  const today = dayjs();
  const begin = today.hour(20).endOf('hour').unix();
  const end = today.add(1, 'day').hour(6).startOf('hour').unix();

  return {
    begin,
    end,
  };
};

// 取得した天気リストの中から、最高気温、最低気温、最高湿度を選ぶ
const selectOne = (weathers: any): WeatherInfoInterface => {
  let min = 100;
  let max = -20;
  let humidity = 0;

  for (const nightWeather of weathers) {
    min = min < nightWeather.main.temp ? min : nightWeather.main.temp;
    max = max > nightWeather.main.temp ? max : nightWeather.main.temp;
    humidity = humidity > nightWeather.main.humidity ? humidity : nightWeather.main.humidity;
  }

  return { min, max, humidity };
};

// 気温を基に、最適な布団を選択する
const getBestBedClothes = (weather: WeatherInfoInterface): BedclothesInterface => {
  let best = 'タオルケット';
  if (weather.min < 10) {
    best = '羽毛布団 + 毛布';
  } else if (weather.min < 15) {
    best = '羽毛布団';
  } else if (weather.min < 20) {
    best = '薄手の掛け布団';
  }

  return {
    best,
  };
};

数日使ってみて

初日は暖かく、タオルケットをオススメされました。2日目は少し寒かったので薄手の掛布団でした。
朝起きてみて、ちょうどいい感じだったので意外と使えるかも。

try1.jpg

try2.jpg

ぱっと使ってみて改善したいところをいくつかピックアップしました。
そのうちアップデートするかも。

  • 返事が文字だけだと寂しいので、イラストとか入れたい
  • 前日の気温と比較して、暖かくなるのか寒くなるのかメッセージを追加したい
  • プル型だと見なくなるので、定時にプッシュ送信したい
  • 機械感が強いので、キャラを設定してみる
  • 寝床内気象なるパラメータがあるらしい。
    これはさすがに天気APIでの取得は無理なので、LINE Thingsを使って布団内の気温・湿度を取得したい
     →温度だけですが測定してみました。「快適な睡眠を目指して寝床内気象を測定する #sleeptech」 (2019/10/22更新)

使ってくれる方がいれば、ぜひフィードバックください。(LINEで友達登録してね)
qr.png

更新履歴

  • 2019/10/19 LINEBOTにリッチメニューを追加しました。
  • 2019/10/21 ソースコードをTypeScript化&リファクタしました。
  • 2019/10/22 寝床内気象の温度取得記事を書きました。リンク追加。
Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away