M5Stackで防犯センサーを作ってみた

# 背景

M5Stack Grayを使って何かできないかと考え、ふと閃いたのが、防犯センサーです。

M5Stack Grayには、9軸センサー(加速度、ジャイロ、地磁気)とスピーカーが搭載されています。
ジャイロセンサーをトリガーにして、音を鳴らすようにすれば、防犯センサーの完成です。

備忘録に近い内容ですが、ご容赦ください。

-----

# M5Stackのセットアップ

M5Cloudから、MicroPythonを使えるようにします。

## 開発環境

- MacBook Air (13-inch, 2017)
- macOS Mojave 10.14.3

## 下準備

ここら辺を参考に、MacからM5Stackを認識できるようにします。あと、esptool.pyをインストールしておきます。
https://qiita.com/inachi/items/d23b9e39551836cddb09
https://qiita.com/hmmrjn/items/2b2da09eecffcbdbad85

## ファームウェアの書き込み

やり方が悪い可能性が高いのですが、[GitHub公式のファームウェア](https://github.com/m5stack/M5Cloud/tree/master/firmwares)では、上手くいかなかったです。ファームウェアを書き込んでも、画面に何も表示されなかった...

色々試した結果、Windows環境を使って、M5Burnerに入っているM5Cloud v0.4.0を書き込んだ所、上手くいきました。

というわけで、Macの方で、[M5Burner Windows版](https://m5stack.oss-cn-shenzhen.aliyuncs.com/resource/software/M5Burner.zip)をダウンロードします。

unzipしてから、**M5Burner/firmwares/M5Cloud/v0.4.0/firmware_0x1000.bin**を、適当な場所にコピー&ペーストします。そして、esptool.pyを使って、ファームウェアを書き込みます。

```sh
$ esptool.py --chip esp32 --port /dev/tty.SLAB_USBtoUART erase_flash
$ esptool.py --chip esp32 --port /dev/tty.SLAB_USBtoUART write_flash --flash_mode dio -z 0x1000 firmware_0x1000.bin
```

## M5Cloudの設定

あとは、下記記事を参考に、M5StackがWi-Fiに繋がるよう設定します。
https://qiita.com/cyubachi/items/3481e0697a718b586d09

-----

# 防犯センサーの詳細

こんな感じの仕様にしました。

## 仕様

ジャイロセンサーの値が設定値(センサー値で1000)を超えた場合、SDカードに保存したWaveファイルを再生する。

## 実装したコード

下記のサイトを参考にして、コードを作成しました。
https://qiita.com/cyubachi/items/707f77be425217391aae
http://blog.akanumahiroaki.com/entry/2018/10/27/235000


作成したコードは、以下の通りです。


```python:main.py
from m5stack import *
from machine import I2C, I2S, reset
from mpu9250 import MPU9250
import os, time, wave
from math import atan2, cos, sin, sqrt

# -----------------------------------------------------------------------------
# Initialize I2S port
# -----------------------------------------------------------------------------
i2s = I2S(mode = I2S.MODE_MASTER | I2S.MODE_TX | I2S.MODE_DAC_BUILT_IN,
          rate = 16000,
          bits = 16,
          channel_format = I2S.CHANNEL_ONLY_RIGHT,
          data_format = I2S.FORMAT_I2S_MSB)

# -----------------------------------------------------------------------------
# This is a DemoClass for 9DOF Sensor(MPU9250).
# -----------------------------------------------------------------------------
class SensorDemo:
    def __init__(self):
        # Initialize MPU9250(I2C)
        i2c = I2C(sda = 21, scl = 22)
        self.sensor = MPU9250(i2c)
    
    def acceleration(self):
        # Get Acceleration Data(ax, ay, az)
        return self.sensor.acceleration
        
    def gyro(self):
        # Get Gyro Data(gx, gy, gz)
        return self.sensor.gyro
        
    def magnetic(self):
        # Get Magnetic Data(mx, my, mz)
        return self.sensor.magnetic

    def pitch(self, ax, ay, az):
        # Calc Pitch
        return atan2(ay, sqrt(ax**2 + az**2)) * 57.3
    
    def roll(self, ax, ay, az):
        # Calc Roll
        return atan2(-ax, sqrt(ay**2 + az**2)) * 57.3
    
    def yaw(self, ax, ay, az, mx, my, mz):
        # Calc Yaw from Pitch & Roll
        yh = (my * cos(self.roll(ax, ay, az))) - (mz * sin(self.roll(ax, ay, az)))
        xh = ( (mx * cos(self.pitch(ax, ay, az))) + 
               (my * sin(self.roll(ax, ay, az)) * sin(self.pitch(ax, ay, az))) + 
               (mz * cos(self.roll(ax, ay, az)) * sin(self.pitch(ax, ay, az))) )
        return atan2(yh, xh) * 57.3

# -----------------------------------------------------------------------------
# Play Wave file with I2S
# -----------------------------------------------------------------------------
def wav_player(fname):
    # Open Wave file
    wav = wave.open(fname)
    i2s.set_dac_mode(I2S.DAC_RIGHT_EN)
    i2s.sample_rate(wav.getframerate())
    i2s.bits(wav.getsampwidth() * 8)
    i2s.nchannels(wav.getnchannels()) 
    i2s.volume(30)

    while True:
        # Set buffer size
        data = wav.readframes(1024)
        if len(data) > 0:
            # Play Wave file
            i2s.write(data)
            print('.', end='')
        else:
            # Close Wave file and Open Wave file (for Loop sequence)
            wav.close()
            wav = wave.open(fname)
            i2s.set_dac_mode(I2S.DAC_RIGHT_EN)
            i2s.sample_rate(wav.getframerate())
            i2s.bits(wav.getsampwidth() * 8)
            i2s.nchannels(wav.getnchannels()) 
            
        if buttonB.pressedFor(1.0):
            # Run Kill process
            wav.close()
            i2s.stop()
            lcd.clear()
            lcd.setCursor(0, 0)
            lcd.setColor(lcd.WHITE)
            lcd.print('Release Button!', 0, 0, color=lcd.RED)
            lcd.print('Please wait a second.', 0, fh * 2)
            reset()

# /////////////////////////////////////////////////////////////////////////////
# Main Code starts from here.
# /////////////////////////////////////////////////////////////////////////////

# Initialize Display
lcd.clear()
lcd.setCursor(0, 0)
lcd.setColor(lcd.WHITE)

# Get fontSize
fw, fh = lcd.fontSize()

# Display Labels
lcd.print('Acceleration', 0, 0)
lcd.print('Gyro', 0, fh * 4)
lcd.print('Magnetic', 0, fh * 8)
lcd.print('Pitch, Roll, Yaw', 0, fh * 12)

# Initialize DemoClass
sD = SensorDemo()

while True:
    # Get SensorData
    ax, ay, az = sD.acceleration()
    gx, gy, gz = sD.gyro()
    mx, my, mz = sD.magnetic()
    
    # If exceeded the threshold value, break while loop and play Wave file.
    if abs(gx) > 1000 or abs(gy) > 1000 or abs(gx) > 1000:
        lcd.print(' Motion was detected!', 0, fh * 17, color=lcd.RED)
        break
    
    # Display SensorData
    lcd.print(' ax: {:+.1f}'.format(ax), 0, fh * 1)
    lcd.print(' ay: {:+.1f}'.format(ay), 0, fh * 2)
    lcd.print(' az: {:+.1f}'.format(az), 0, fh * 3)
    
    lcd.print(' gx: {:+.1f}'.format(gx), 0, fh * 5)
    lcd.print(' gy: {:+.1f}'.format(gy), 0, fh * 6)
    lcd.print(' gz: {:+.1f}'.format(gz), 0, fh * 7)
    
    lcd.print(' mx: {:+.1f}'.format(mx), 0, fh * 9)
    lcd.print(' my: {:+.1f}'.format(my), 0, fh * 10)
    lcd.print(' mz: {:+.1f}'.format(mz), 0, fh * 11)
    
    lcd.print(' pitch: {:+.1f}'.format(sD.pitch(ax, ay, az)), 0, fh * 13)
    lcd.print(' roll : {:+.1f}'.format(sD.roll(ax, ay, az)), 0, fh * 14)
    lcd.print(' yaw  : {:+.1f}'.format(sD.yaw(ax, ay, az, mx, my, mz)), 0, fh * 15)
    
    time.sleep_ms(20)

# Mount SD Card and Play Wave file.
os.mountsd()
wav_player('/sd/(再生するWaveファイルを指定する)')
```

## コードの説明

防犯センサーとは関係ないですが、画面上にセンサーからの情報を表示するようにしました。
表示する内容は、以下の通りです。

### 表示内容

- 加速度(X軸, Y軸, Z軸)
- ジャイロ(X軸, Y軸, Z軸)
- 地磁気(X軸, Y軸, Z軸)
- ロール(加速度から計算)
- ピッチ(加速度から計算)
- ヨー(ロール・ヨーから計算)

ジャイロセンサーの閾値(センサー値で1000)を超えた場合、whileループを抜けて、```wav_player```を実行します。```wav_player```の部分では、SDカードに格納されたWaveファイルを読み込み、ループ再生します。

Waveファイルの再生を止めるには、Bボタンを1.0秒以上、長押しします。すると、メッセージを表示した後に、システムが再起動されます。

## 注意点

M5Stackでは、SDカードしか認識できないみたいです。試した限りでは、32GBのSDHCカードは認識されませんでした。

なお、上記コードの動作確認は、2GBのSDカードで行いました。

## 使い方

1. SDカードに、任意のWaveファイルを書き込む。
2. main.pyをM5Stackに書き込む。
3. SDカードを、M5Stackに挿入する。
4. 平らな場所にM5Stackを置き、電源を入れる。

M5Stackを持ち上げると、音が鳴ります。