この記事は「オフィス上空の天気予報を通知してみた。」シリーズの第4回です。
「オフィス上空の天気予報を通知してみた。」シリーズ(全4回)
- 【まとめ】 オフィス上空の天気予報を通知してみた。
- 【第1回】 オフィスを出る前に外が雨かを知れるシステムを作ってみた。
- 【第2回】 降雨予測の的中確率を通知してみた。
- 【第3回】 OpenWeatherMapで天気予報を取得。
- 【第4回】 slackで1日の天気予報を通知してみた。(←現在、閲覧中。)
はじめに
前回から、出勤前にオフィス周辺の天気を確認できる機能の実装を行っています。
前回の記事、「OpenWeatherMapで天気予報を取得。」ではOpenWeatherMapの説明をしました。
今回は実際にOpenWeatherMapを使ってslackに1日の天気予報を通知してくれる機能を追加していきます。
構成
AWS Lambda上で今まで降雨情報の取得を行っていた関数(test_rain)とは別に、1日の天気予報を取得し、通知するための関数(test_weather_day)を作ります。
1日の天気予報(test_weather_day)のフロー
営業日の朝8時にOpenWeatherMapから3時間ごとの天気予報を取得
通知メッセージの作成
slackに1日の天気予報を送信
タイムスタンプ情報の更新
S3に更新したタイムスタンプ情報を保存
不要なメッセージの削除
現在の降雨情報(test_rain)のフロー
営業時間中、10分毎にYOLP(Yahoo! Open Local Platform)の気象情報APIからTOWNのオフィス上空の降雨情報を取得
S3からログファイルを読み込む
通知メッセージの作成とログファイルのアップデート
slackへの降雨情報メッセージの送信or更新(朝9時のみ送信)
タイムスタンプ情報の更新(朝9時のみ)
S3にログファイルを保存
不要なメッセージの削除
仕様
- 朝に1日の天気予報を通知
- 3時間毎の天気、最高気温、最低気温を表示
- 雨が降る可能性がある場合はメンションをつける
- slackへのメッセージ送信時に、不要なメッセージを削除
- 常に今日の天気予報が上に、現在の降雨情報が下に表示されるようにする
S3の準備
今回はS3に1日の天気予報と現在の降雨情報のタイムスタンプを記録しておくファイルを用意しました。これは不要なメッセージを削除するときに使います。このファイルに記録されているタイムスタンプを持つメッセージのみを残し、その他のメッセージは全て削除するようにします。
ts.json
{ "Now_ts": "1545963845.003600", "Day_ts": "1551330916.000200" }
準備するもの
- OpenWeatherMapのAPIキー
- 降雨情報を取得したい場所の緯度・経度
- slack appとToken
- 投稿するchannelのID
- AWSのアクセスキー、シークレットアクセスキー
- Amazon S3のバケット
- タイムスタンプを記録するファイル:”ts.json”
これらの取得方法がわからない場合はこのシリーズの前の記事を参照してください。
コード
1日の天気予報(test_weather_day)
import json import datetime import requests import boto3 from slackclient import SlackClient ### OpenWeatherMap ### API_KEY = 'XXXXXXXXXX' LAT = 'XXXXXXXXXX' LON = 'XXXXXXXXXX' API_URL = 'http://api.openweathermap.org/data/2.5/forecast?lat={0}&lon={1}&units=metric&lang=ja&APP\ ID={2}' ### SLACK ### slack_token = "XXXXXXXXXX" slack_client = SlackClient(slack_token) ### channel_id for 降雨情報 ### channel_id="XXXXXXXXXX" ### AWS ### accesskey = "XXXXXXXXXX" secretkey = "XXXXXXXXXX" region = "ap-northeast-1" s3_client = boto3.client('s3',region_name=region) file_name = "ts.json" bucket_name = "weather-notification-log" def lambda_handler(event, context): temp_max = None temp_min = None message = "" mention = "" rain = 0 test_item = "" today = datetime.datetime.now().strftime('%Y-%m-%d') date_text = datetime.datetime.now().strftime("%-m/%-d") ### 天気予報取得 ### url = API_URL.format(LAT, LON, API_KEY) forecastData = requests.get(url).json() ### タイムスタンプ情報の読み込み ### ts_log = json.loads(s3_client.get_object(Bucket=bucket_name,Key=file_name)["Body"].read()) ts_now = ts_log["Now_ts"] for item in forecastData['list']: forecastDate = item["dt_txt"].split()[0] forecastTime = item["dt_txt"].split()[1].split(":")[0].rjust(2) if(today == forecastDate and 9 <= int(forecastTime) <= 18): weatherId = item["weather"][0]["id"] temperature = item['main']['temp'] if 'rain' in item and '3h' in item['rain']: rain = 1 ### 最高・最低気温の算出 ### if(temp_max == None): temp_max = temperature else: if(temperature >= temp_max): temp_max = temperature if(temp_min == None): temp_min = temperature else: if(temperature <= temp_min): temp_min = temperature ### 天気予報メッセージの作成 ### message = make_message(message, weatherId, forecastTime) ### 曜日の取得 ### weekday = get_weekday(today) if(rain): mention = "<!channel> \n" ### メッセージ全文 ### text = (mention + "本日の天気予報をお知らせします。\n" + date + "(" + weekday + ")" + " " + "最低気温:" + str(temp_min) + "℃" + "/" + "最高気温:" + str(temp_max) + "℃" + message) ### Slackにメッセージを送信 ### slack_response = send_message(slack_client, channel_id, text) ### タイムスタンプ情報を更新 ### ts_log = json.dumps(update_ts_log(ts_log, slack_response),indent=4) s3_response = send_s3(file_name,ts_log) ts_day = slack_response["ts"] ### 降雨情報以外のメッセージを削除 ### for history in get_history(slack_client,channel_id)["messages"]: ts=history["ts"] if (ts != ts_now and ts != ts_day): response=delete_message(slack_client,channel_id,str(ts)) return slack_response def make_message(message, weatherId, forecastTime): message += "\n" + forecastTime + "時 : " if(weatherId == 800): message += ":sunny:" elif(801 <= weatherId <= 804): message += ":cloud:" elif(weatherId == 500): message += ":closed_umbrella:" elif(weatherId >= 501): message += ":umbrella_with_rain_drops:" elif(600 <= weatherId <700): message += ":snowman:" elif(200 <= weatherId < 300): message += ":thunder_cloud_and_rain:" elif(300 <= weatherId < 400): message += ":umbrella_with_rain_drops:" elif(700 <= weatherId <800) return message def channel_list(client): response = client.api_call("channels.list") if channels['ok']: return channels['channels'] else: return response def send_message(client, channel_id, message): response = client.api_call( "chat.postMessage", channel=channel_id, username="Weather", text=message, icon_emoji=":rainbow:" ) return response def update_message(client, channel_id, message,ts): response = client.api_call( "chat.update", channel=channel_id, text=message, ts=ts ) return response def delete_message(client,channel_id,ts): response = client.api_call( "chat.delete", channel=channel_id, ts=ts ) return response def get_history(client,channel_id): response = client.api_call( "channels.history", channel=channel_id, ) return response def send_s3(filename,body): response = s3_client.put_object( Bucket=bucket_name, Key=filename, Body=body ) return response def get_weekday(today): week_list = ["月","火","水","木","金","土","日"] d = datetime.datetime.strptime(today, "%Y-%m-%d") return week_list[d.weekday()] def update_ts_log(ts_log, response): ts_log["Day_ts"] = response["ts"] return ts_log
現在の降雨情報(test_rain)(変更箇所のみ抜粋)
def lambda_handler(event, context): today = datetime.datetime.now().strftime("%Y%m%d") post_time = today + "0850" ### タイムスタンプ情報の読み込み ### ts_log = json.loads(s3_client.get_object(Bucket=bucket_name,Key=file_ts)["Body"].read()) ts_now = ts_log["Now_ts"] ts_day = ts_log["Day_ts"] 〜〜〜〜〜〜〜〜〜中略〜〜〜〜〜〜〜〜〜 ### Slackにメッセージを送信 ### if(post_time == now_time): slack_response = send_message(slack_client, channel_id, message) ### タイムスタンプ情報を更新 ### ts_log = json.dumps(update_ts_log(ts_log, slack_response),indent=4) ts_now = slack_response["ts"] send_s3(file_ts,ts_log) else: slack_response = update_message(slack_client, channel_id, message, ts_now) ### S3にファイルをアップロード ### s3_response_analyse = send_s3(file_analyse,body_analyse) s3_response_log = send_s3(file_log,body_log) ### 降雨情報以外のメッセージを削除 ### for history in get_history(slack_client,channel_id)["messages"]: ts=history["ts"] if (ts != ts_now and ts != ts_day): response=delete_message(slack_client,channel_id,str(ts)) return slack_response 〜〜〜〜〜〜〜〜〜中略〜〜〜〜〜〜〜〜〜 def update_ts_log(ts_log, response): ts_log["Now_ts"] = response["ts"] return ts_log
ちなみに…
test_weather_day関数では今日の予報だけがほしいのでdatetimeを使って日付を取得しているのですが、最初は望むような動作をしてくれませんでした。調べてみたら原因はdatetimeの変数宣言の場所とタイムゾーンでした。という話を以下の記事に書いているので同じところで躓いている場合は以下の記事を参考にしてみてください。
トリガーの設定
今回は営業日である平日の毎朝8時に実行するようにしたいのでCloudwatchEventsをトリガーにしてtest_weather_day関数を実行するようにスケジュール式を設定します。トリガーの設定方法については以下の記事を参考にしてください。
- cron式
cron(0 23 ? * SUN-THU *)
cron式が思い通りの動作をしてくれない場合は式がUTC時刻で正しく記述されているかを確認してみてください。私がcron式のUTC時刻で躓いた話を下の記事で紹介しています。
動作確認
営業日の毎朝8時に以下のようにメッセージが送信されるようになりました。
さいごに
これで出勤前にオフィス周辺の天気を確認できるようになりました。雨の日はメンションもつけてくれるので傘を忘れる心配も減りますね。
「オフィス上空の天気予報を通知してみた。」シリーズ(全4回)
- 【まとめ】 オフィス上空の天気予報を通知してみた。
- 【第1回】 オフィスを出る前に外が雨かを知れるシステムを作ってみた。
- 【第2回】 降雨予測の的中確率を通知してみた。
- 【第3回】 OpenWeatherMapで天気予報を取得。
- 【第4回】 slackで1日の天気予報を通知してみた。(←現在、閲覧中。)