「ラズパイ天気予報　〜for AIT〜」作った！

# システムの概要
### どんなものを作ったか
　大学にいる時に雨が降ってきそうな天候の場合、さっさと家に帰るべきなのか、まだ大学にいてもいいのかわからないですよね。
　今回作ったシステムは、そんな問題を手軽に解決するためのものです。三時間後の天気予報をツイートするBotアカウントを作ることで、ユーザがまだ大学にいていいのか、判断を補助するためのシステムを目指しました。

### システム構成
　ラズベリーパイで自校である愛知工業大学周辺地域の天気を取得し、作った専用アカウントでその情報をわかりやすい形にフォーマットして呟きます。
　システムの構成は以下の通りです。

 - Rasberry Pi model B+
 - 専用のTwitterアカウント
 - ThingTwiite
 - OpenWeatherMap

<img width="667" alt="スクリーンショット 2019-05-31 21.32.57.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/219573/2bf85003-9524-5867-1da9-d44e32861e3d.png">

　Rasberry PiがOpenWeatherMapにリクエストを送信し、受け取ったレスポンス（天気情報）を整形して、その情報をThingTweetAPIに投げます。ThingTweetAPIはAPIキーデータを元にして、紐づけられた天気予報用の専用アカウントでその情報をツイートします。
　この流れで天気予報を呟きます。

 APIについてはこちらの記事、書籍にお世話になりました。
書籍：[Raspberry Pi クックブック　369p「ThingSpeakを使ってツイートする」](https://www.oreilly.co.jp/books/9784873118116/)
記事：[【WebAPI】OpenWeatherMapで3時間ごとの天気を取りたい【json】](https://qiita.com/marksard/items/472000594ca83b64f00c)


# システム詳細
## ソースコード
　ソースコードの全貌はGithubに載せてあります。

　[ラズパイ天気予報！　〜for AIT〜](https://github.com/furukawaTakumi/weatherForecaster)

　APIキーは残ったままになっていますが、削除＆再生成しているので問題ありません。

　ここでは、こだわったポイントについて載せようと思います！

### Main文中のbeSureToDoメソッド
```Main.py
def beSureToDo(city):
    while(True):
        try:
            data = weatherApi.getDataLatLon(city[0],city[1],1)
            weatherData = extractor.DataSetting(data)
            twitter.twitte( twitext.Build(weatherData) )
        except apie.APIgetDataException:
            sleep(60)
            continue
        break
```

　やっていることとしてはまず、設定した市の緯度経度を元にJson形式の天気データを取得し、そこから抽出した天気情報をまとめて`weatherData`に入れます。そして、ツイートするテキストを組み立てる役割を持ったクラスのインスタンスである`twitext`に天気情報を渡して組み立てたツイート文章をTwitterクラスに投げてツイートさせています。
　注目してほしいのは、天気情報の取得に失敗することで発生する`APIgetDataException`をキャッチして一分後にもう一度天気情報を取得できないかトライしているところです。こうすることで、APIにリクエストが集中して情報が取れなかった場合にも処理を継続して確実に天気情報をツイートすることができます！

### WeatherApiUiクラス
```WeatherApiUi.py
# coding: utf-8

import json
import requests
import APIgetDataException as apide

class WeatherApiUi:
    def __init__(self):
        self.api_key = '***************api - key************'
        self.baseUrl = 'http://api.openweathermap.org/data/2.5/forecast?&APPID={0}'

    def getDataLatLon(self, lat, lon, cnt=0):
        api_page = self.baseUrl + '&lat={1}&lon={2}'
        if 0 < cnt and cnt <= 30:
            api_page = api_page + '&cnt=' + str( cnt )
        url = api_page.format(self.api_key, lat, lon)
        req_response = requests.get( url )
        res = json.loads(req_response.text)
        if "message" in res and 'Internal error: 500001' == res["message"]:
            raise apide.APIgetDataException("APIサービスが混んでいるようです")
        return res

    def getDataZip(self, zip, cnt=0):
        api_page = self.baseUrl + '&zip={1}'
        if 0 < cnt and cnt <= 30:
            api_page = api_page + '&cnt=' + str( cnt )
        url = api_page.format(self.api_key,zip)
        req_response = requests.get( url )
        res = json.loads(req_response.text)
        if "message" in res and 'Internal error: 500001' == res["message"]:
            raise apide.APIgetDataException("APIサービスが混んでいるようです")
        return res
```
　`getDataLatLon`は緯度経度の情報に従って、その地点の天気情報をAPIから取得するためのメソッドで、`getDataZip`は郵便番号を元に天気情報を取得するメソッドです。取得件数も指定できるようになっています。
　OpenWeatherMapからの情報取得方法には都市名を指定してデータを取得するものや都市IDを指定してデータを取得するものもありますが、それらも当クラスに同様にように実装すれば、OpenWeatherMapからデータを取得するクラスとしてかなり充実したものとなると思います。

　作成段階では、当クラスに必要な天気情報だけを抽出するメソッドも実装するという案ありましたが、メソッド数が多くなりすぎてしまうのでそれは断念しました。しかも、よくよく考えると、このクラスの役割が「APIからデータを取得すること」だけでなく、「必要な情報を抜き出すこと」も持ってしまうところでした。あぶなかったです。

教訓としては**メソッド数が多くなりすぎるときはそのクラスが責務を負い過ぎている可能性が高い**というところでしょうか。

　オブジェクト指向でいうところの
 - 単一責務の法則
　を実践できた瞬間でした。

## システムの運用方法
　OpenWeatherMapでは、天気情報の更新が0,3,6,9,12,15,18,21時に行われるので、３時間毎の定期処理実行が必要になってきます。これには`cron`コマンドを利用しました。

### `cron`とは？
　`cron`（クーロン）とは、Linux標準の定期処理実行を行う常駐プログラムです。`crontab`と呼ばれる設定ファイルに定期で実行するコマンドを書き足すことによって利用することができます。
　常駐プログラムという特性上、`crontab`に実行するコマンドを記述するだけで、処理が定期実行されるようになります。

　cronコマンドについてはRasberry Piを触るまで知らなかったので、色々参考にしました。

[クーロン(cron)をさわってみるお](https://qiita.com/katsukii/items/d5f90a6e4592d1414f99)
[cronでスクリプトを定期実行させるときに注意したい4つのこと](https://orebibou.com/2014/12/cron%E3%81%A7%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%97%E3%83%88%E3%82%92%E5%AE%9A%E6%9C%9F%E5%AE%9F%E8%A1%8C%E3%81%95%E3%81%9B%E3%82%8B%E3%81%A8%E3%81%8D%E3%81%AB%E6%B3%A8%E6%84%8F%E3%81%97%E3%81%9F/#3LANG)
[Cron が走っているかどうか確認する](https://qiita.com/katzueno/items/ca416d32b901d64a344d)
[cronの停止コマンド](https://560days.com/lab/2012/06/000899.php)
[RaspberryPi cronが効かない → /etc/crontab のパーミッションと所有者を見直して解決](http://min117.hatenablog.com/entry/2017/06/25/103621)

　調査の結果、以下のコマンドをcrontabに記述しておけば良いことがわかりました。

`LANG=jp_JP.UTF-8
0 0,3,6,9,12,15,18,21 * * * python3 /etc/pi/hoge/fugafuga/Main.py`

　一行目では、スクリプト中に登場する日本語を文字化けさせないための環境変数設定です。`cron`は実行時にユーザの環境変数を引き継がないので、スクリプト中に日本語を使っている場合は文字化けを防ぐために書いた方が良いとのことです。

### `cron`について学んだこと一覧
 - `cron`は、設定した定期処理を実行してくれる常駐プログラムである
 - 設定の変更は`crontab -e`で行える（ユーザ権限の設定ファイル）
 - `crontab -r`で設定が吹き飛ぶ（消去される）ので、注意する。
 - 管理者用の`crontab`は`/home/pi`階層から`../../etc/crontab`の位置にある。
 - なおこのファイルを変更する場合はテキストエディタで設定する。例としては`sudo nano crontab`である。
 - `cron`が実行されているかは`service cron status`で調べられる。
 - `cron`を起動するコマンドは、`service cron start`
 - `cron`を再起動するコマンドは`service cron restart`
 - `cron`を停止するコマンドは`service cron stop`
 - `cron`に設定されているコマンドを確認するためのコマンドは`crontab -l`
 - ネットの情報を見ていると`crond`、`cron`の二種類が散見されるが、Rasberry Piの場合は`cron`である。
 - 権限を付与しないとコマンドを実行できない場合がある。
 - `cron`の時間指定は`分 時 日 月 曜日`の順
 - コマンドは絶対パスで記述した方が良い。

# できあがったもの
　こんな感じになりました！

　[ラズパイ天気予報！　~for AIT~のTwitterアカウント](https://twitter.com/ait_for)
　![スクリーンショット 2019-06-02 13.44.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/219573/3ed5bf4f-5b09-fbf1-64d7-63b67692255a.png)


　Rasberry PiがAITの周辺の市の天気予報を三時間おきにつぶやいています。
　これで早く帰らないといけないかがわかりますね（予報だから当たるとは言ってない）

# 参考文献
- python
[Pythonのdatetimeで日付や時間と文字列を変換（strftime, strptime）](https://note.nkmk.me/python-datetime-usage/)
[Pythonで日付から曜日や月を文字列（日本語や英語など）で取得](https://note.nkmk.me/python-datetime-day-locale-function/)
[Pythonの数値の桁数や丸めなどの操作のまとめ](https://www.headboost.jp/python-how-to-handle-digits/#22)

- `cron`について
[クーロン(cron)をさわってみるお](https://qiita.com/katsukii/items/d5f90a6e4592d1414f99)
[cronでスクリプトを定期実行させるときに注意したい4つのこと](https://orebibou.com/2014/12/cron%E3%81%A7%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%97%E3%83%88%E3%82%92%E5%AE%9A%E6%9C%9F%E5%AE%9F%E8%A1%8C%E3%81%95%E3%81%9B%E3%82%8B%E3%81%A8%E3%81%8D%E3%81%AB%E6%B3%A8%E6%84%8F%E3%81%97%E3%81%9F/#3LANG)
[Cron が走っているかどうか確認する](https://qiita.com/katzueno/items/ca416d32b901d64a344d)
[cronの停止コマンド](https://560days.com/lab/2012/06/000899.php)
[RaspberryPi cronが効かない → /etc/crontab のパーミッションと所有者を見直して解決](http://min117.hatenablog.com/entry/2017/06/25/103621)

- git
[gitでのヤバイ！を取り消す方法](https://qiita.com/ritukiii/items/74ee3274c3f218511a0c#%E7%95%AA%E5%A4%96%E7%B7%A8-git-reset---hard%E3%82%92%E5%8F%96%E3%82%8A%E6%B6%88%E3%81%97%E3%81%9F%E3%81%84)

- API
[Raspberry Pi クックブック 369p「ThingSpeakを使ってツイートする」](https://www.oreilly.co.jp/books/9784873118116/)
[【WebAPI】OpenWeatherMapで3時間ごとの天気を取りたい【json】](https://qiita.com/marksard/items/472000594ca83b64f00c)