釣りにArduinoを導入してM5StickCで投げ釣りのアタリを判定する

**There is a great translator who wrote the English version of this article. Please take a look at the link.**

- [Smart Fishing Rod with M5StickC by vany5921](https://www.hackster.io/hotstaff/smart-fishing-rod-with-m5stickc-51c74f)


M5StickC
![top.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/232003/19548cb3-479a-a7eb-a9c7-d8192fb04c1a.png)

ADXL345(Grove接続)
![adxl345s.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/232003/83c3eb1f-b392-d233-b996-2506e1289b82.png)

# 経緯

投げ釣りを久しぶりにやってみると、向こう合わせですが、竿先の動きを見てないといけないのです。
過去に見てなくて大物がかかって海に竿が飛び込んだことがあるんです。
その時は友人が奇跡的にルアーで海から拾ってくれたものの
竿先をずっと見てアタリを待つのが結構面倒でした。

そこで電子工作でどうにかできないか思い調べたところ、

**M5StickC**なるものを発見！

スイッチサイエンスで早速購入しアタリ待ちシステム構築を試みることに。

# いざ購入！

というわけで[スイッチサイエンス](https://www.switch-science.com/)で購入したのは次の通り

![tsurido_items.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/232003/2b9ff7a3-8caf-7754-6976-452ed56ff56e.png)

**【注意】送料を無料にするためだけに足の長いピンソケットが導入されております。（一応M5StickCに刺さる場所はある）**

作戦としては、竿先にGroveケーブルなるものでつながる加速度センサー(重さ4g)を取り付けて
その値をM5StickCでBluetooth送信、それをパソコンで受信して加速度のグラフをプロットする予定です。

画面がなくて加速度センサーとかも入ってるAtom（重さ14g）というのもあり魅力的ですが、初めてなんで
とりあえず今回は、いろいろ遊べそうなM5Stickと外部センサーの組み合わせを選択することにしました。


バッテリーが大きくなった新型の**M5StickC+**というのも出てるみたいなんですが、在庫が０だったので旧型にしました。
ところでこれだけで開発できるんだろうかと思いますが、実際できるみたいです。
いろいろ入ってて、すぐに使えるM5StickCは素晴らしい商品です。

# M5StickCについて

RaspberryPiはv1の頃に触ってなんとなくわかるのですが
Arduinoについては名前くらいしか全然知りませんでした
昔にPICをやろうとしてライターから作らないといけなかったりして
途中で挫折したことがあったのですが、
調べてみるとArduinoは開発環境が無料ですぐ入れられるらしい
これはできると思い、試しにIDEを入れてみると一発で入り
しかもUSBでつなげれば書き込みもすぐにできるらしい

いい時代になりました。これだと、すぐに使えます。

というわけで、さっそくIDEをインストールし

優秀な先人の記事を参考に

- [M5StickCをはじめよう　Arduino編](https://qiita.com/usashirou/items/3f149727aceb5ffa0f2d)

M5Stickを使う初期設定をして、商品の到着を待ちました。

# M5StickC到着

電源を入れるとスイッチサイエンスのデモスケッチで傾き等が見えるものが動きました。

それでは早速つなげてみます。Groveケーブルで接続したら完成です。

![minimum_s.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/232003/25b3750d-c1c7-7da5-6596-fb037c9006cd.png)

**楽すぎて拍子抜けします。**

センサーに短いケーブルがついていたのでそれを使っていますが、
実戦では最長の2mのケーブルを使ってセンサーを竿先のセンサーとつなぐ予定です。
ADXL345のユニットには軸の方向が印刷してあって後でわかったことですが、開発する時とてもわかりやすいです。


到着後しばらくIDEの使い方であったり書き込みのやり方など、Qiitaの記事でいろいろ学びました。
同じ環境でいろいろな人が作ってるので、単純なものはスケッチをコピーするだけで使えて非常に楽しいです。
とにかく繋げば基本的に動きます。
Bluetoothとシリアル通信のやり方を一通り学んでいざコーディングしました。
といってもBluetoothにセンサーデーターを飛ばすだけです。

# 釣り竿側のコード

作戦としては釣り竿側は加速度センサーの値を高速で送信する必要があるので
プログラムとしてはまずは単純に送信する機能だけに絞りました。
画面には単に$x,y,z$方向の加速度の２乗のルート(３次元ベクトルの長さ)、つまり以下の式

```math
\begin{align}
\vec{A}&=(A_x, A_y, A_z)\\
A&=\sqrt{{A_x}^2 + {A_y}^2 + {A_z}^2}
\end{align}
```
で計算される、加速度ベクトルの大きさのみを表示するようにしてます。
加速がない状態では地球の加速度1gの値が表示されている状態です。
つまり動く方向を限定せずに、いづれかの方向に動けばこの$A$の値は1gからずれるという寸法です。
今回は単純にこの値だけを使って判定するとします。

処理能力に余裕があるようだったらその他の機能を後でつけるつもりです。

## 使用したライブラリ

ADXL345のライブラリはいくつかあるみたいですが、今回はArduinoIDEで検索して出てきた

- [Accelerometer ADXL345(Seeed Studio)](https://www.arduinolibraries.info/libraries/accelerometer-adxl345)

というのを使いました。
今回は使用しませんでしたが、このライブラリには他にも
タッチ検出・自由落下検出などの作り付け機能がいろいろ入ってるみたいです。
簡単に使えるみたいなので、応用範囲は広そうです。

- ライブラリ
   - Accelerometer ADXL345
- 作者
   - Seeed Studio
   - [Website(githubのリポジトリ)](https://github.com/Seeed-Studio/Accelerometer_ADXL345)

## 完成したスケッチ

そういうわけで、出来上がったM5StickC側のコードは以下

``` c++:tsurido.ino

/*****************************************************************************/
//  Function:    Get the accelemeter of X/Y/Z axis and print out on the 
//                  serial monitor and bluetooth.
//  Usage:       This program is for fishing. Use the accelerometer at the end
//               of the rod to see if the fish is caught. Acceleration is 
//               transmitted in real time via Bluetooth and can be monitored 
//               from a laptop.
//  Hardware:    M5StickC + ADXL345(Grove)
//  Arduino IDE: Arduino-1.8.13
//  Author:  Hideto Manjo     
//  Date:    Aug 9, 2020
//  Version: v0.1
//
//  This program is free software; you can redistribute it and/or
//  modify it under the terms of the GNU Lesser General Public
//  License as published by the Free Software Foundation; either
//  version 2.1 of the License, or (at your option) any later version.
//  This library is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
//  Lesser General Public License for more details.
//  You should have received a copy of the GNU Lesser General Public
//  License along with this library; if not, write to the Free Software
//  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
//
/*******************************************************************************/
#include <M5StickC.h>
#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLE2902.h>
#include <ADXL345.h>
#include <Wire.h>

#define SERVICE_UUID        "8da64251-bc69-4312-9c78-cbfc45cd56ff"
#define CHARACTERISTIC_UUID "deb894ea-987c-4339-ab49-2393bcc6ad26"
#define DEVICE_NAME         "Tsurido"
#define LCD_ROTATION        0      // 画面の回転角度 = 90 * LCD_ROTATION, 180度のときは2を指定 [半時計回り]
#define DELAY               50     // microsecondsで指定
#define SERIAL              true   // シリアル出力(USB)の有無
#define BAUDRATE            115200 // シリアル出力ボーレートの設定

#define SCALAR(x, y, z)     sqrt(x*x + y*y + z*z)

BLEServer* pServer = NULL;
BLECharacteristic* pCharacteristic = NULL;
ADXL345 adxl;

// bleの接続がある時はtrueとなる
bool deviceConnected = false;

int x = 0;
int y = 0;
int z = 0;
int scalar;
char msg[128];


class MyServerCallbacks: public BLEServerCallbacks {
                void onConnect(BLEServer* pServer) {
                        M5.Lcd.setCursor(10, 110);
                        M5.Lcd.println("Con");
                        deviceConnected = true;
                }

                void onDisconnect(BLEServer* pServer) {
                        M5.Lcd.setCursor(10, 110);
                        M5.Lcd.println("   ");
                        deviceConnected = false;
                }
};

void setup_adxl345() {
        adxl.powerOn();
}

void setup_ble(){
        BLEDevice::init(DEVICE_NAME);
        BLEServer *pServer = BLEDevice::createServer();
        pServer->setCallbacks(new MyServerCallbacks());
        BLEService *pService = pServer->createService(SERVICE_UUID);
        pCharacteristic = pService->createCharacteristic(
                                  CHARACTERISTIC_UUID,
                                  BLECharacteristic::PROPERTY_READ |
                                  BLECharacteristic::PROPERTY_WRITE |
                                  BLECharacteristic::PROPERTY_NOTIFY |
                                  BLECharacteristic::PROPERTY_INDICATE
                          );
        pCharacteristic->addDescriptor(new BLE2902());

        pService->start();
        BLEAdvertising *pAdvertising = pServer->getAdvertising();
        pAdvertising->start();
}

void setup() {
        M5.begin();

        // M5StickCのピンアサインを確認 SDA=32, SDL=33
        // Groveケーブルを指す場所に印刷してあります
        Wire.begin(32, 33);

        Serial.begin(BAUDRATE);
        setup_adxl345();
        setup_ble();
        M5.Lcd.setRotation(LCD_ROTATION);
        M5.Lcd.fillScreen(BLACK);
        M5.Lcd.setTextSize(2);
        M5.Lcd.setCursor(10, 0);
        M5.Lcd.println("Tsuri");
        M5.Lcd.println("  do ");
}

void loop() {
        M5.update();

        // ADXL345から加速度データを読み込む
        adxl.readXYZ(&x, &y, &z);

        // ベクトルの大きさ（スカラー量）に変換する
        scalar = SCALAR(x, y, z);
        M5.Lcd.setCursor(0, 60);
        M5.Lcd.println("Acc");
        M5.Lcd.printf("  %d\n", scalar);
        sprintf(msg, "Ax, Ay, Az, A: %d, %d, %d, %d", x, y, z, scalar);
        
        if (SERIAL)
            Serial.println(msg);

        if (deviceConnected) {
                pCharacteristic->setValue(msg);
                pCharacteristic->notify();
        }

        delay(DELAY);
}

```

参考にさせてもらったQiitaの記事

- [M5StackでBLEを使う](https://qiita.com/nsawa/items/2afd4ae9c10af87d0133)

起動すると加速度の測定を初めて、bluetoothから接続すれば

```
"Ax, Ay, Az, A: 2, 2, 255, 16"
```

のような感じで、シリアルプロッターと同じフォーマットで50msごとに文字列を送信します。

この例ですとAz方向におよそ$1g$が測定されている感じです。

加速度の精密な値を測定する必要はないので今回はゲイン等の調整は無視してセンサーの整数値をそのまま送信しています。


# ノートパソコン側（クライアント）の制作

つぎはノートパソコン側を作るとします。

bluetoothで文字列をパースするところからまず作ります。




## パース関数
```python:パース関数部分
@staticmethod
def _parse(data):
    # まず:の位置でそれぞれ分ける
    labels, values, = str(data).split(":")
    # ,でsplitしたあとstripで前後の空白を取る
    labels = [label.strip() for label in labels.split(',')]
    values = [value.strip() for value in values.split(',')]

    # labelsをvaluesと同じ長さに揃える
    if len(labels) > len(values):
        labels = labels[0:len(values)]
    else:
        labels += (len(values) - len(labels)) * [""]

    return labels, values
```
例としては

```python
"Ax, Ay, Az, A: 2, 2, 255, 255"
```

みたいな文字列が送られてくると

```python:parse関数の戻り値
labels = ["Ax", "Ay", "Az", "A"]
values = ["2", "2", "255","255"]
```

の２つのリストを返します。
シリアルプロッターでもラベルはなくても動くみたいなので同じように
長さが違う場合はvaluesに合わせるようにしています。

## アタリの判定

センサーデーターからアタリを判定するために
単純に手動で設定したしきい値で判定しようとしてたのですが
よく考えたら風の影響等もあるのでもうすこし考えてみたところ
外れ値を見つけたらいいんじゃないかと思いつきました。

![400px-Standard_deviation_diagram.svg.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/232003/b89ed8e9-4269-ef47-80ad-1ea3eb496481.png)
(wikipediaより標準偏差の図)

つまり、センサーデーターの平均値と標準偏差を計算しておいて
例えば2σ以上平均値から離れたら、アタリという感じです。

numpyには平均値も標準偏差も関数があるので簡単に作ることができました。

```python:モジュールの一部
# まず平均値と標準偏差を計算する
mean = self.y.mean()
std = self.y.std()

# 平均値からのズレ量だけにする
# このdiffをプロットすると最低値は常に0となり使いやすい
diff = np.abs(self.y - mean)

# ズレ量と標準偏差をアタリ判定関数へ渡す
self._check_warning(diff, std)
```
当たり判定関数は以下のようになっています。
オーバーレンジとしているのはグラフの縦軸上限も、
この標準偏差を基準に決めてるためです。

```python:当たり判定関数

# self.sigma = [3, 5]
def _check_warning(self, diff, std):
    if time.time() - self.last_ring > 2:
        if diff[-1] > self.sigma[1] * std:
            # オーバーレンジになった時
            self.last_ring = time.time()
            SoundPlayer.play("warning2.mp3")
        elif diff[-1] > self.sigma[0] * std:
            # アタリ
            self.last_ring = time.time()
            SoundPlayer.play("warning1.mp3")

```

last_ringは前回当たり判定した時刻で、次の判定まで２秒間は待つようにしています。

## 判定のテスト

下の図はとりあえず暫定的にグラフの表示レンジは5σで
アタリの判定しきい値は3σとして動かしてみたところです。

手でセンサーを動かしてます、黄色い線(3σ)を超えた時とレンジオーバー(5σ)になると音がでます。
感度調整も標準偏差によって自動で計算されるため結構いい感じに判定できそうな感じです。

![sense.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/232003/58a978c0-5454-331e-96e1-d84198be5e14.png)
（画像はクライアントの作り途中の様子です。1000カウントのあたりで手で加速度センサーを揺らしています。
すると加速度の偏差が3σのラインを超えて音がなります。）

というわけでなんとかできてそうです。



## そのほかの細かい部分

クライアントを作っていていくつか気になったことを紹介します。重要な部分ではないので、急ぐ方は[次の章](#ノートパソコン側のコード)に飛んでください。

### プロットウィンドウを閉じたら一緒に終了するようにする

サブスレッドで受信している部分が生きてるとウィンドウを閉じても
無限に生成されるので終了できません。
そこで一緒に終了できるようにしました。

```python:プロットウィンドウ閉じた時の判定部分
from matplotlib import _pylab_helpers

if _pylab_helpers.Gcf.get_active() is None:
    # ウィンドウが閉じられた時はclosedフラグを立てる
    self.closed = True
    return
```

```python:判定を受けてbluetoothのループから抜ける部分
# プロッターのclosedフラグがfalseのときだけsleepし続ける。
while not PLOTTER.closed:
    time.sleep(0.1)

print('Disconnecting to device...')
device.disconnect()

print("Stop.")
return 0
```

PLOTTER.closedフラグを判定しながらsleepしてます。
bluetoothのスレッドはサブスレッドで実行されているため
永久にwaitしてる場合はCtrl-Cで止まらなくなって大変でした。
ひとまずの解決方法です。
こうすることでとりあえずwindowsを閉じると0.1秒後にはdevice.disconnect()して終了できます。

### バックグラウンドでmatplotlibをプロットする

pauseでプロットするとプロットはできるのですが、
ウィンドウがアクティブになってしまいます。
そこで調べると解決方法があったので、コードを修正しました。
結構探したので記しておきます。

```python:置き換え前
plt.pause(self._pause)
```
これだと、ウィンドウが常にアクティブになる

```python:置き換えたコード
plt.ion()

plt.gcf().canvas.draw_idle()
plt.gcf().canvas.start_event_loop(self._pause)
```
こうするとバックグラウンドで表示できる

### アタリ判定時の音の再生

音の再生は過去の記事のものを使いました。

[Pythonでmp3のバックグラウンド再生する方法](https://qiita.com/hotstaff/items/26d76a18635e3fe5ad07)

### 竿先の角度の計算

どうせなら竿先の角度も計算すると
魚が食らいついてすこしずつ竿が曲がった場合にも備えられそうなので
加速度から傾斜角度も計算してみました。

計算方法は[AN-1057: 加速度センサーによる傾きの検出 - Analog Devices](https://www.analog.com/media/jp/technical-documentation/application-notes/AN-1057_jp.pdf)
に乗ってました。

それによると、加速度センサーの値を次のように

```math
\begin{align}
 \theta \mathrm{[rad]} &= \arctan{\frac{A_x}{A_y}} \\
 \phi \mathrm{[rad]} &= \arccos{\frac{A_z}{\sqrt{{A_x}^2 + {A_y}^2 + {A_z}^2}}}
\end{align}
```

極座標に変換すればそれぞれの角度が得られるようです。この２つの角度がどの部分を表しているかは上のリンク先に図がありますのでご参照ください。
注意するべきはこの時の左辺は単位がラジアンになりますので、角度に直すには$180/\pi$を掛ける必要があります。

Goove接続のADXL345の$x$軸方向を竿の方向に合わせるとちょうど$\theta$が竿先の角度を表します。
注意点としては$\theta$はADXL345の$y$軸を重力方向に向けるか、逆にするかで正負が変わるので
マイナスの値をとったりしますが今回必要なのは水平面との角度だけなので計算結果を使うときにはnp.absで絶対値をとっています。

ゼロで割ってエラーにならないようにif文で計算を分岐させてます。
大きさが０の場合も$\phi$がエラーにならないように０で返しています。

```python:竿先の角度の計算

@staticmethod
def angle(ax, ay, az):
    # ayが0のときは0で割って不定形になるので固定値を代入
    if ay != 0:
        theta = np.arctan(ax / ay) * 180 / np.pi
    else:
        if ax > 0:
            theta = 90
        elif ax < 0:
            theta = -90
        else:
            theta = 0

    # phiの場合も分母がゼロになる場合は計算せず代替値を代入
    scalor = np.sqrt(ax*ax + ay*ay + az*az)
    if scalor != 0:
        phi = np.arccos(az / scalor) * 180 / np.pi
    else:
        phi = 0

    return theta, phi
```


# ノートパソコン側のコード完成

紆余曲折の末とりあえずできたのがこちら

<details><summary>ソースコード(cliend.py)</summary><div>

```python
""" Trurido client

Copyright 2020 Hideto Manjo.
Licence: LGPL
"""

import time
import uuid
import numpy as np

from matplotlib import pyplot as plt
from matplotlib import _pylab_helpers

from pydub import AudioSegment
import simpleaudio

import Adafruit_BluefruitLE

plt.style.use('dark_background')
plt.rcParams['toolbar'] = 'None' 

SERVICE_UUID = uuid.UUID("8da64251-bc69-4312-9c78-cbfc45cd56ff")
CHAR_UUID    = uuid.UUID("deb894ea-987c-4339-ab49-2393bcc6ad26")


class SoundPlayer:
    """SoundPlayer module."""

    @classmethod
    def play(cls, filename, audio_format="mp3", wait=False, stop=False):
        """Play audio file."""

        if stop:
            simpleaudio.stop_all()

        seg = AudioSegment.from_file(filename, audio_format)
        playback = simpleaudio.play_buffer(
            seg.raw_data,
            num_channels=seg.channels,
            bytes_per_sample=seg.sample_width,
            sample_rate=seg.frame_rate
        )

        if wait:
            playback.wait_done()


class Plotter:
    def __init__(self, interval=1, width=200, pause=0.005, sigma=(5, 7),
                 angle=True, xlabel=False, ylabel=True):
        # field length
        self.__fieldlength = 4

        # main plot config
        self._target_index = 3
        self._interval = interval
        self._pause = pause
        self._width = width
        self._angle = angle
        self.sigma = sigma   # [warning, overrange]

        self.count = 0
        self.closed = False # plotter end flag
        self.last_ring = time.time() + 3

        self.t = np.zeros(self._width)
        self.values = np.zeros((self.__fieldlength, self._width))

        # initial plot
        plt.ion()
        self.fig = plt.figure()
        self.fig.canvas.set_window_title('Tsurido Plotter')
        if self._angle:
            self.ax1 = self.fig.add_subplot(2, 1, 1)
        else:
            self.ax1 = self.fig.add_subplot(1, 1, 1)

        self.li, = self.ax1.plot(self.t, self.values[self._target_index],
                                 label="Acc", color="c")
        self.li_sigma = self.ax1.axhline(y=0)

        if xlabel:
            plt.xlabel("count")

        if ylabel:
            self.ax1.set_ylabel(self.li.get_label())

        if angle:
            self.tip_angle = np.zeros(self._width)
            # self.ax2 = self.ax1.twinx()
            self.ax2 = self.fig.add_subplot(2, 1, 2)
            self.li2, = self.ax2.plot(self.t, self.tip_angle,
                                      label="Rod angle", color="r")
            self.ax2.set_ylabel(self.li2.get_label())

    @staticmethod
    def _parse(data):
        labels, values, = str(data).split(":")
        labels = [label.strip() for label in labels.split(',')]
        values = [value.strip() for value in values.split(',')]

        # Make the lists of labels and values the same length, fill in the blanks
        if len(labels) > len(values):
            labels = labels[0:len(values)]
        else:
            labels += (len(values) - len(labels)) * [""]

        return labels, values

    @staticmethod
    def angle(ax, ay, az):
        if ay != 0:
            theta = np.arctan(ax / ay) * 180 / np.pi
        else:
            if ax > 0:
                theta = 90
            elif ax < 0:
                theta = -90
            else:
                theta = 0

        phi = np.arccos(az / np.sqrt(ax*ax + ay*ay + az*az)) * 180 / np.pi

        return theta, phi

    def _store_values(self, values):
        self.t[0:-1] = self.t[1:]
        self.t[-1] = float(self.count)
        self.count += 1

        self.values[:, 0:-1] = self.values[:, 1:]
        self.values[:, -1] = [float(v) for v in values]

    def _store_angle(self, values):
        angle = self.angle(*[float(v) for v in values[:3]])
        self.tip_angle[0:-1] = self.tip_angle[1:]
        self.tip_angle[-1] = np.abs(angle[0])

    def _check_warning(self, diff, std):
        if time.time() - self.last_ring > 2:
            if diff[-1] > self.sigma[1] * std:
                # over range
                self.last_ring = time.time()
                SoundPlayer.play("sfx/warning2.mp3")
            elif diff[-1] > self.sigma[0] * std:
                # warning
                self.last_ring = time.time()
                SoundPlayer.play("sfx/warning1.mp3")

    def _plot(self, y, std):
        self.li.set_xdata(self.t)
        self.li.set_ydata(y)

        self.li_sigma.set_ydata([self.sigma[0] * std, self.sigma[0] * std])

        self.ax1.set_ylim(0, self.sigma[1] * std)
        if self.count != 1:
            self.ax1.set_xlim(np.min(self.t), np.max(self.t))

        if self._angle:
            self.li2.set_xdata(self.t)
            self.li2.set_ydata(self.tip_angle)
            self.ax2.set_xlim(np.min(self.t), np.max(self.t))
            self.ax2.set_ylim(self.tip_angle.mean() - 10,
                              self.tip_angle.mean() + 10)

        plt.gcf().canvas.draw_idle()
        plt.gcf().canvas.start_event_loop(self._pause)

    def received(self, data):
        if _pylab_helpers.Gcf.get_active() is None:
            # end flag
            self.closed = True
            return

        # Parse
        _labels, values = self._parse(data)

        # Store value as new values
        self._store_values(values)

        # Calculate and store rod tip angle
        if self._angle:
            self._store_angle(values)

        # Calculate standard deviation
        y = self.values[self._target_index]
        std = y.std()

        # Calculate deviation from average value
        diff = np.abs(y - y.mean())

        # Sounds a warning when the deviation exceeds a specified value
        self._check_warning(diff, std)

        # Since plotting is slow, accurate graphs can be drawn by
        # thinning out the display.
        if self.count % self._interval == 0:
            self._plot(diff, std)

        # print('{0}'.format(data))

def main():
    BLE.clear_cached_data()

    adapter = BLE.get_default_adapter()
    adapter.power_on()
    print('Using adapter: {0}'.format(adapter.name))

    print('Disconnecting any connected devices...')
    BLE.disconnect_devices([SERVICE_UUID])

    print('Searching device...')
    adapter.start_scan()

    try:
        device = BLE.find_device(name="Tsurido", timeout_sec=5)
        if device is None:
            raise RuntimeError('Failed to find device!')
    finally:
        adapter.stop_scan()

    print('Connecting to device...')
    device.connect()

    print('Discovering services...')
    device.discover([SERVICE_UUID], [CHAR_UUID])

    uart = device.find_service(SERVICE_UUID)
    chara = uart.find_characteristic(CHAR_UUID)

    print('Subscribing to characteristic changes...')
    chara.start_notify(PLOTTER.received)

    # wait
    while not PLOTTER.closed:
        time.sleep(0.1)

    print('Disconnecting to device...')
    device.disconnect()

    print("Stop.")
    return 0


if __name__ == '__main__':
    BLE = Adafruit_BluefruitLE.get_provider()
    PLOTTER = Plotter(interval=4, angle=True)

    BLE.initialize()
    BLE.run_mainloop_with(main)

```
</div></details>

結構長くなってしまったので折りたたみました。

# 動作風景

最終的に下のようなクライアントができました。

![sense.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/232003/3f0f4c56-9e88-98c6-c8be-43df67168c73.png)
（上に加速度の偏差、下に竿先の角度を表示しています。加速度の偏差が黄色い線を超えると当たり判定となり音で知らせます。上の例は手で揺らしています。）

実際に魚が釣れるとどうなるのか未知数ですが、これは楽しみになってきました。

# いざ実戦投入！

さてそれでは、テストができたので実戦投入です。

魚は釣れるのかを実際に検証していきます。

時は８月１９日、夕方、まずは竿にセット

そして車にノートパソコンとM5StickCを乗せ釣り場に向かいます。

とても釣りの装備とは思えませんが・・・電子機器のほうが多いです。

餌の青虫（ゴカイ）を釣具屋で調達し釣り場に到着しました。

## 竿に取り付ける

竿にはビニールテープでM5StickCについてきたネジ止め用のものを

竿に貼り付けました。

![DSCF1376.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/232003/54cd0d66-813d-baab-2cda-3954b462d415.jpeg)
（竿にセットした図）

M5StickCの内部電池だと心配なのでUSBケーブルで

4000mAhの１００円ショップの５００円バッテリーを取り付けています。

結構使えます。

センサーもビニールテープで取り付けました。海に飛んでいかないか心配です。

![DSCF1377.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/232003/8132a4cc-9336-81e7-fc68-ae5c5210e16f.jpeg)
（ピンぼけしています。すみません。）

# 釣り開始

本当は投げ釣りの竿はもっと角度をつけて設置しないと魚に引かれて、海に落ちる可能性があるんですが

今回は持ち物がないので折りたたみ椅子に立てかけました。ちょっと角度が浅いので心配です。

さて、本当に釣れるのか・・・

## 竿をセットしてクーラーの効いた車内でアタリを待ちます。

竿をセットして車内でノートパソコンを見守る

![DSCF1380.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/232003/b59c2b13-eee7-060d-8d68-90bf740434aa.jpeg)

車内でモニターを見ながら釣りをするのは初めてです。

# 本当に釣れました

映像に撮ろうと準備をしているその時・・・

ピピピという音がなりました。！！


アタリです。

いや風の影響かな・・・と思って竿を見ると

本当にアタっていました。

そのまま慌てて引き上げてみると・・・

![DSCF1381.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/232003/2fa76217-a835-f13f-5247-779e3d19c619.jpeg)

１５cmくらいのチヌでした。

いやーやばいです。竿を投入してから２分位で

本当に想定したとおりのアタリ音が鳴って、実際にアタッて魚が釣れてしまいました。

残念ながら釣れた瞬間のグラフのログを残していなかったので

感動的な最初のアタリの記録は残っていませんが

このシステムまじで釣れます。

しかも、グラフを見ていると

目視よりも細かいアタリが見えていそうです。

これは本当に使えます。！！

おそらく大成功と言えるでしょう、すでにアタリ判定は実用レベルに到達しています。

# 実際に当たるとどういう波形が出るか

カメラ等をセットした後は実際に釣れませんでしたが

何度かアタリがありました。

その時の様子です。下の画像はOBSで釣道というゲームの画像に似せてプロットなどを合成してみたものです。

水温等はセンサーの値ではないですが、M5StickCを使えばそれもリアルタイムに測定できるかもしれないです。

![vlcsnap-2020-08-19-19h55m44s854.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/232003/f42b4245-6838-bf65-d34a-26a6edd5f39a.png)

少し見にくい画像で申し訳ないですが、右下にPlotterのプロットがあって、

加速度の偏差がしきい値を超えて反応しているのが見えます。

下の傾斜計は地震の波形のように周期的な振動をしています。

ここまではっきり見えるとは思いませんでした。

魚の種類や合わせのタイミングも見分けられるかもしれないです。

# 実際に釣りをしてみてわかったこと

Groveケーブルが思ったより重たく竿にセットするのに時間がかかります。

これはある程度想定していましたが、

おそらくGroveケーブルを使わない方法を考えたほうが良さそうです。

M5Atomというものがありこちらも小さいながら、

加速度センサーはしっかり入っていて、14gと軽いです、

電池なしですが、usbの電源ケーブルを先端から引くだけで使えそうです。

今後はそちらを作ってみようと思います。

次回はそちらの記事になると思います。

これは投げ釣りに革命が起きそうなレベルです。

M5StickCこれはアイディア次第でかなり面白いデバイスです。

ぜひ皆さんもいろいろ作ってみてください。！！

M5Stackはいろんなセンサーがすぐに使えるので今まで使えなかったところで

IoTが使えるようになるためとても楽しいです。

夏休みの自由研究にもいいんじゃないでしょうか。

# プロッターとスケッチの公開

スケッチ等とクライアントプログラムを公開しました。

掲載したものよりすこしだけ機能がついています。

[Github(hotstaff/tsurido)](https://github.com/hotstaff/tsurido)

M5StickCとADXL345とノートパソコンがあれば実験できるかと思いますので

興味がある方はぜひどうぞ。

何かの参考に慣れば幸いです。