バス釣り特有の釣果自慢したい欲を利用して釣果報告書を作成する

# バス釣りジャンキーの私がプログラミング覚えたら

私にとって、バス釣りとはエントリーしたフィールドの様々な状況を分析し、最適なルアーやテクニックを使用してターゲットを仕留めるロジカルな狩りゲーです。

現場の水温や気温、気圧変化、風、照度、周りの釣り人の数、等々・・・
自然相手のスポーツなので感覚に頼る部分も多いのですが、いくつか数値化出来そうな要素があります。

そういう事なら、釣れた時の状況や結果が出たメソッドを記録していけば、どのような条件が揃ったときに、何をすれば釣れるのかが見えてくるのではないだろうか・・・

しかし実際のところ、釣れた時の状況をメモして帰って日記なりExcelなりにまとめるのって、面倒すぎて、過去にやろうと思ったときは2回ぐらいで挫折しました。

釣れて記念写真撮ったらそのまま友達にLINEする時の勢いのまま記録取れたら続けられそうなのになぁ・・・


よし、あいつにやってもらおう。

### あいつって誰よ
こいつです。

https://qiita.com/kohey0701/items/12b676457224d0564477

# おいこら、技術記事だろうが！
すみません、前置きが長くなりました。今回作りたいものは、**釣れた時の情報を友達にLINEで自慢するついでに釣果報告書を自動で作成するシステム**です。

以前開発した上記のLINEBOTは温湿度計と高性能気圧計機能(自称)を備えています。
ルアー釣りの経験がある方は共感頂けるかもしれませんが、釣り仲間相手につい、「どんな状況でどんなテクで釣れた！」ってのを言いたくなるんです。そういうもんなんです。(私だけ？)

では、早速これらを実現するための方法を考えてみましょう！

# どうやって実現する？
1. LINEBOTに「釣れた」と連絡すると状況や結果を質問してくれる
1. １の質問に答えていけば、釣果情報をBOTが勝手にスプレッドシートに記録してくれる
1. いちいち時間や気温まで報告するの正直めんどいのでBOT側でやっといてもらう

まぁ難しく考える必要は無く、オウム返しBOTの仕組みを応用して、こちらから送ったメッセージをリスト(配列)に放り込んで、データ型をキャストしてスプシをアップデートすればしまいだと思います。多分。

冒頭でご紹介したシステムをベースにして、これらの機能を順番に実装していきましょう！

## 使用機材、技術のおさらい
センサ部分は半自作です。材料と使用技術をまとめます。

#### 前提スキル
- Pythonで基本的なプログラミングが出来る
- ラズパイでLED光らせたことがある
- LINEBOTを作ったことがある
- スプレッドシートをPythonで操作したことがある
- ~~リザーバーで30センチ以上のブラックバスを釣ったことがある~~

上記一つでも当てはまる方の次の開発の参考になれば幸いです。
#### 材料
- [Raspberry Pi4](https://www.amazon.co.jp/%E3%83%A9%E3%82%BA%E3%83%99%E3%83%AA%E3%83%BC%E3%83%91%E3%82%A44-%E3%82%B3%E3%83%B3%E3%83%94%E3%83%A5%E3%83%BC%E3%82%BF%E3%83%BC%E3%83%A2%E3%83%87%E3%83%ABB-Raspberry-Computer-Model/dp/B07WR5W2D6)
- [気圧センサモジュール](https://akizukidenshi.com/catalog/g/gK-13460/)
- [温湿度センサモジュール](https://akizukidenshi.com/catalog/g/gM-07003/)

温湿度センサの作り方(ラズパイ編)

https://qiita.com/kohey0701/items/9627746a277e70fd8039

#### 利用技術
- [LineMessagingAPI](https://developers.line.biz/ja/services/messaging-api/)
- [GoogleSheetsAPI v4](https://developers.google.com/sheets/api)
- [Python3](https://docs.python.org/ja/3/)
- [Google Cloud Platform](https://cloud.google.com/gcp/?utm_source=google&utm_medium=cpc&utm_campaign=japac-JP-all-en-dr-bkws-all-all-trial-e-dr-1009882&utm_content=text-ad-none-none-DEV_c-CRE_498991269734-ADGP_Hybrid%20%7C%20BKWS%20-%20EXA%20%7C%20Txt%20~%20GCP%20~%20General_%20Core%20Brand-KWID_43700060620875331-kwd-87853815-userloc_1009567&utm_term=KW_gcp-ST_gcp&gclid=CjwKCAjw3pWDBhB3EiwAV1c5rNNH4DQ94s3gi1zKN9IjWx3xm8A_-lttvxgczeXRfdZB-v_WqyDnMRoCKNgQAvD_BwE&gclsrc=aw.ds)



# 準備はOK?開発を始めよう！
[こちらの記事](https://qiita.com/kohey0701/items/12b676457224d0564477)で作成した`app.py`をベースに改良します。ソースコードを全文掲載していますので宜しければご参考までに。
オウム返しBOTの開発経験がある方はそのまま読み続けて頂いても問題ありません。


### スロットフィリング！

```python

#ユーザからメッセージを受け取ったら発動
@handler.add(MessageEvent, message=TextMessage)
def handle_message(event):
    
    #ユーザからのコメント
    my_comments = event.message.text

    #BOTのリプライメッセージの生成
    comment = create_message(my_comments)

    リプライの送信
    line_bot_api.reply_message(
        event.reply_token, TextSendMessage(text=comment))

```
先ずは処理の流れを確認します。1メッセージにつき、1返信をする流れとなっています。なので今回は**スロットフィリング型**の質問回答形式を採用します。
**スロットフィリング型**とは、確認したい項目が全て埋まるまで確認を繰り返手法です。必須項目が漏れなく得られる事と、ユーザ側も何を記録したいのか迷わないことがメリットで、今回の用途には最適です。

では、上記ソースコードの`create_message()`に`my_comments`を渡して、処理を実装しましょう！

```python:slot
global record

    if my_comments in ["釣れた", "釣れたで"]:
        botRes = "おめでとうございます！\nでは記録しましょう！\nサイズを教えて下さい"
    elif botRes in ["おめでとうございます！\nでは記録しましょう！\nサイズを教えて下さい", "ではもう一度！\nサイズを教えて下さい"]:
        record = []
        record.append(my_comments)
        botRes = "場所は？"
    elif "場所は？" in botRes:
        record.append(my_comments)
        botRes = "ヒットルアーは？"
    elif "ヒットルアーは？" in botRes:
        record.append(my_comments)
        botRes = "メソッドは？"
    elif "メソッドは？" in botRes:
        record.append(my_comments)
        botRes = "天気は？"
    elif "天気は？" in botRes:
        record.append(my_comments)
        botRes = "コメントはありますか？"
    elif "コメントはありますか？" in botRes:
        record.append(my_comments)
        botRes = "確認します！\nサイズ:{0[0]}cm\n場所:{0[1]}\nヒットルアー:{0[2]}\nメソッド:{0[3]}\n天気:{0[4]}\nコメント:{0[5]}\nです！\n記録していいですか？".format(record)

```
う～ん、わかりやすくするために文字列ベタ書きにしとこうと思っていたのですがかえって見づらいような気も・・・~~ライブラリ化するスキルがショボいんだろ~~

このように、「釣れた」のキーワードをgetしたら「記録しよう、サイズ教えて」とリプライします。
サイズと言われるといやでもサイズを言いたくなりますので、釣れた魚のサイズをLINEします。
それをgetかつ前回のリプライが「サイズ教えて」の時に次の質問をします。
あとは事前に宣言してあるリスト(ここではrecord変数)に`append`していくわけです。
頭こんがらがりそうですが、仕組自体はこんな感じで、この流れを繰り返して回答をリストに貯めていきます。後程解説しますが、リストの`record`変数は関数の外で初期化しておき、global宣言しておきます。

上記コードによりデータの取得が出来たら、ユーザに確認できるようにしましょう。

```python
    elif botRes in ["確認します！\nサイズ:{0[0]}cm\n場所:{0[1]}\nヒットルアー:{0[2]}\nメソッド:{0[3]}\n天気:{0[4]}\nコメント:{0[5]}\nです！\n記録していいですか？".format(record), "私にわかる言葉つかってください！\n\n確認します！\nサイズ:{0[0]}cm\n場所:{0[1]}\nヒットルアー:{0[2]}\nメソッド:{0[3]}\n天気:{0[4]}\nコメント:{0[5]}\nです！\n記録していいですか？".format(record)] and my_comments in "よろしく":
        sheet_update()
        record = init_record(record)
        botRes = "では記録しておきます！\n環境データはこっちでまとめときますね"
    elif botRes in ["確認します！\nサイズ:{0[0]}cm\n場所:{0[1]}\nヒットルアー:{0[2]}\nメソッド:{0[3]}\n天気:{0[4]}\nコメント:{0[5]}\nです！\n記録していいですか？".format(record), "私にわかる言葉つかってください！\n\n確認します！\nサイズ:{0[0]}cm\n場所:{0[1]}\nヒットルアー:{0[2]}\nメソッド:{0[3]}\n天気:{0[4]}\nコメント:{0[5]}\nです！\n記録していいですか？".format(record)] and my_comments in "やり直す":
        record = init_record(record)
        botRes = "ではもう一度！\nサイズを教えて下さい"
    elif botRes in ["確認します！\nサイズ:{0[0]}cm\n場所:{0[1]}\nヒットルアー:{0[2]}\nメソッド:{0[3]}\n天気:{0[4]}\nコメント:{0[5]}\nです！\n記録していいですか？".format(record), "私にわかる言葉つかってください！\n\n確認します！\nサイズ:{0[0]}cm\n場所:{0[1]}\nヒットルアー:{0[2]}\nメソッド:{0[3]}\n天気:{0[4]}\nコメント:{0[5]}\nです！\n記録していいですか？".format(record)] and my_comments not in ["よろしく", "やり直す", "やめとく"]:
        botRes = "私にわかる言葉つかってください！\n\n確認します！\nサイズ:{0[0]}cm\n場所:{0[1]}\nヒットルアー:{0[2]}\nメソッド:{0[3]}\n天気:{0[4]}\nコメント:{0[5]}\nです！\n記録していいですか？".format(
            record)
    elif botRes in ["確認します！\nサイズ:{0[0]}cm\n場所:{0[1]}\nヒットルアー:{0[2]}\nメソッド:{0[3]}\n天気:{0[4]}\nコメント:{0[5]}\nです！\n記録していいですか？".format(record), "私にわかる言葉つかってください！\n\n確認します！\nサイズ:{0[0]}cm\n場所:{0[1]}\nヒットルアー:{0[2]}\nメソッド:{0[3]}\n天気:{0[4]}\nコメント:{0[5]}\nです！\n記録していいですか？".format(record)] and my_comments in "やめとく":
        record = init_record(record)
        botRes = "記録せずに終了します"

```

文字列が長すぎて見づらいですがご容赦ください・・・
必要なデータをユーザからgetし終わったら内容を確認させます。
内容がＯＫなら「よろしく」的な事を言えば報告書作成モードになり、「やり直し」的な事を言えば最初からやり直せます。やっぱりやめたい時は「やめとく」的な事を言えばリストを初期化して通常モードに戻ります。
確認用テキストには`.format`を使用して、文字列にリストの要素を埋め込んでおけばＯＫです。

ちょっとイメージ沸きづらいと思うので、一連の処理の実行結果を記録した貴重な映像をご覧ください。

![GIF-210401_195640.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653352/1d7be7dd-ebd4-1cdf-a7a9-74b05789cf97.gif)

こんな感じで情報取って確認してくれます。確認モード中、指定の文字列以外を受け取ると再度確認してきます。
確認してくれた内容に間違いがあったらやり直し処理として、冒頭の質問に戻り、その際リストを初期化します。

スプレッドシートのアップデートかデータの破棄が終わったらリスト`record`を初期化します。

#### リスト初期化時の注意
リストの初期化は以下のように記述し、空の文字列を埋めておきます。

```python
record = [""] * 6
```

スロットフィリング式処理の記述で文字列にリストの要素を`.format`で埋め込んでいます。
そのため、空のリストを宣言し、処理終了後の初期化の際に空にしてしまうと、この一連の処理を行う関数実行時、`botRes`の条件を一式比較しますので、その際、リストに`append`していないまま条件比較が通ると、「`.format()`してるところ、何もありまへんで！」とPythonさんに怒られます。空白×6でいいので、リストの中をきちんと埋めておく必要がある点に注意してください。

では次はスプレッドシートに記録する部分を見てみましょう！

# スプレッドシートを自動更新！
スプレッドシート自動更新の詳細な説明は下記を参考にしてください。

https://qiita.com/kohey0701/items/d9071044be123c931e48

先程スロットフィリング処理で取得したユーザからの情報と、LINEBOTちゃんが持っている情報を組み合わせて、釣果報告書シートに書き込んでもらいましょう。

![シート2の内容.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653352/2b8ab70d-483e-9494-9110-0aa7f44bf337.jpeg)

シート2には環境情報が色々入っていますので、この中で釣りに使えそうなデータといえば、

- 温湿度データ
- 気圧データ
- 気圧変化値

が参考になりそうです。~~ほぼ全部やんけ~~
ソルトルアーフィッシングだとタイドグラフも欲しいですね！まぁそのときはAPIどついてやればなんとかなりそうです。今回は手持ちのデータを使いましょう。では、コードを見て行きます！

```python
def sheet_update():
    sheet2, sheet3 = get_sheet_all()
    values = sheet2.get_all_records()
    length = sheet3.get_all_records()
    last = values[-1]
    sheet3.update(f"A{len(length) + 2}:K",
                  [[str(create_datetime()), float(last["temp"]), float(last["humidity"]), float(round(last["pressure"])), float(last["change_pre"]), str(record[4]), str(record[1]), str(record[2]), str(record[3]), float(record[0]), str(record[5])]])

```

シート2(各センサのリアルタイムデータログ)のデータを拾ってきて、その中で欲しいデータのキーを指定し、一番新しいデータをシート3(釣果報告書)にアップします。
このシート3をアップデートする処理に、先ほどの`record`を放り込んでまとめておきます。魚のサイズは小数点第一位まで見る事も多いのでfloat型にキャストしておきましょう。
この部分、記述がもっさりしてる感じが気になるので、もう少しスマートなコーディング方法があればご教示頂ければ幸いです。

では、それぞれの機能が完成したので実行してみます。

### 実行結果

さぁ、LINEBOTに釣果報告をして、記録をしてもらいましょう！
うまくいくかな・・・
![結果.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653352/af959837-8bac-1ab3-41ba-76a948d04795.gif)
![釣果報告書.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653352/0d685cba-ab40-87b1-458b-5fb64f5e6820.jpeg)


おおお！LINEで釣果自慢できるあげく、勝手に釣果報告書を作成してくれました！しかもわざわざ釣れた時の気圧変化や気温も記録してくれている！
これで釣れた時のテンションを犠牲にすることなくデータ収集できちゃいますね！

今回のデータは開発中のものなので一度リセットして、週末早速現地で釣りしてリアルな報告書書きます！(**LINEBOTが**)

#### 欠点
各種センサデータの観測場所は私の家なので、遠征時はあてにならないのが欠点でした笑
まぁ近所に高難易度メジャーフィールドあるので(釣果報告書の場所でお察し)しばらくは地元用で活用しときます。

# おわりに
今回はここまで習得した技術を元に、身近な便利ツールの作成を試してみました。
技術力が上がるほど、思いつきを形に出来るのでもっとプログラミングが楽しくなりますね！

行く行くはこのシステムで貯めた情報を分析して、「今このルアー投げたら釣れるかも！」みたいな情報を通知してくれるシステムにまで発展させたいなと考えています。

思い付きで作っただけのシステムなので、もしこの記事を読まれた方で釣り好きな方がいらっしゃれば、是非こんなデータあった方がいいよとかご教示頂ければ幸いです。

この記事を読んで頂いたエンジニアの方の中に釣り好きの方がいらっしゃったら。
釣果報告書の場所でバス持ちしながらスマホポチポチやってる奴が居たら**多分それ私です。**

## おまけ
デバッグ中の出来事。
![ボケるな.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653352/eea29982-4f1e-28e5-7426-674778ee15fc.jpeg)