犬の水飲み検知IoTをラズパイで作ってみた


我が家には12歳になるトイ・プードルがいます。
高齢犬に分類される年頃なので、飼い主不在時に変わりなく過ごしているかを加速度センサーで検知できる方法を考えてみました。

手法

水飲み用のペットボトルに加速度センサーを付けて、水を飲んだときの振動を検知すると飼い主にお知らせする方法で実現しました。
お知らせはWebhook経由でtweetします。

I2Cを有効にする

センサーは6軸モーションセンサ(加速度3軸+ジャイロ3軸)として MPU-6050を使ったGY-521を使います。
Raspberry PiとはI2Cという通信方式で接続しますので、Raspberry Piのメニューアイコンから

Raspberry Piの設定 > インターフェースの設定

で、I2Cを有効にします。

GY-521をラズパイに接続する

続いてGY-521をラズパイのGPIOピンに接続します。

GY-521 Raspberry Pi
VCC 3.3V
GND GND
SCL GPIO2
SDA GPIO3

接続後、LXTerminalでGY-521が認識されていることを確認します。
MPU-6050のアドレスは「0x68」なので、正常に認識されています。

pi@raspberrypi:~ $ sudo i2cdetect -y 1
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- -- 
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
60: -- -- -- -- -- -- -- -- 68 -- -- -- -- -- -- -- 
70: -- -- -- -- -- -- -- --  

プログラム

ベースのプログラムコードはこちらを参照させていただき、犬の水飲み検知用コードを追加しています。
また、犬の水飲み検知で使用すのは加速度センサーだけなので、ジャイロセンサーの値・気温を取得するコードはコメントアウトしています。

mpu-6050_read.py
#!/usr/bin/python
# -*- coding: utf-8 -*-
import requests
import smbus            # use I2C
import math
from time import sleep  # time module
import os
import datetime

### define #############################################################
DEV_ADDR = 0x68         # device address
PWR_MGMT_1 = 0x6b       # Power Management 1
ACCEL_XOUT = 0x3b       # Axel X-axis
ACCEL_YOUT = 0x3d       # Axel Y-axis
ACCEL_ZOUT = 0x3f       # Axel Z-axis
TEMP_OUT = 0x41         # Temperature
GYRO_XOUT = 0x43        # Gyro X-axis
GYRO_YOUT = 0x45        # Gyro Y-axis
GYRO_ZOUT = 0x47        # Gyro Z-axis

# 1byte read
def read_byte( addr ):
    return bus.read_byte_data( DEV_ADDR, addr )

# 2byte read
def read_word( addr ):
    high = read_byte( addr   )
    low  = read_byte( addr+1 )
    return (high << 8) + low

# Sensor data read
def read_word_sensor( addr ):
    val = read_word( addr )
    if( val < 0x8000 ):
        return val # positive value
    else:
        return val - 65536 # negative value

# Get Temperature
def get_temp():
    temp = read_word_sensor( TEMP_OUT )
    # offset = -521 @ 35℃
    return ( temp + 521 ) / 340.0 + 35.0

# Get Gyro data (raw value)
def get_gyro_data_lsb():
    x = read_word_sensor( GYRO_XOUT )
    y = read_word_sensor( GYRO_YOUT )
    z = read_word_sensor( GYRO_ZOUT )
    return [ x, y, z ]
# Get Gyro data (deg/s)
def get_gyro_data_deg():
    x,y,z = get_gyro_data_lsb()
    # Sensitivity = 131 LSB/(deg/s), @cf datasheet
    x = x / 131.0
    y = y / 131.0
    z = z / 131.0
    return [ x, y, z ]

# Get Axel data (raw value)
def get_accel_data_lsb():
    x = read_word_sensor( ACCEL_XOUT )
    y = read_word_sensor( ACCEL_YOUT )
    z = read_word_sensor( ACCEL_ZOUT )
    return [ x, y, z ]
# Get Axel data (G)
def get_accel_data_g():
    x,y,z = get_accel_data_lsb()
    # Sensitivity = 16384 LSB/G, @cf datasheet
    x = x / 16384.0
    y = y / 16384.0
    z = z / 16384.0
    return [x, y, z]

def ifttt_webhook():
    payload = {"value1": time,
               "value2": sabun_z}
    url = "https://maker.ifttt.com/trigger/{event}/with/key/{your key}"
    response = requests.post(url, data=payload)

### Main function ######################################################
bus = smbus.SMBus( 1 )
bus.write_byte_data( DEV_ADDR, PWR_MGMT_1, 0 )

###取得したデータを保存するためにcsvファイルを準備する。
fileName = '/home/pi/raspberrypi//mpu_data.csv' 

if not os.path.exists(fileName):
    mpuData = open(fileName,'w')
    mpuData.write('time,accel_x,accel_y,accel_z\n')
    mpuData.close()

###初回計測時の比較用データの取得
accel_x,accel_y,accel_z = get_accel_data_g()
old_accel_z = accel_z

###ここから計測開始
while True:
    ###時刻を取得する
    date = datetime.datetime.now()
    time = date.strftime("%m/%d %H:%M:%S")

    ###気温を取得する(利用しないためコメントアウト)
    #temp = get_temp()

    ###ジャイロセンサーの値を取得する (利用しないためコメントアウト)
    #gyro_x,gyro_y,gyro_z = get_gyro_data_deg()

    ###加速度センサーの値を取得する
    accel_x,accel_y,accel_z = get_accel_data_g()

    ###加速度センサーの値をCSVファイルに保存する
    data = [time,accel_x,accel_y,accel_z]
    mpuData = open(fileName,'a')
    mpuData.write(',' .join(map(str,data)) + '\n')
    mpuData.close()

    ###前回取得した値との比較
    sabun_z = old_accel_z - accel_z

    ###差分が閾値を超えたらwebhookに投げて60秒待つ(水飲後、しばらく振動が続くため)
    if abs(sabun_z) > 0.10:
        ifttt_webhook()
        sleep( 60 )

    ###今回取得した値を前回の値に格納する  
    old_accel_z = accel_z

    #次の計測まで10秒間sleep。その後、    
    sleep( 5 )

取得データからの分析→設定

振動の軸

取得したデータを見てみると、z軸の揺れ幅が大きい。
これはセンサーをペットボトルの側面に付けているため、前後の振動が大きいためだと推測されます。
x軸の振動が少ないのは、ペットボトルが固定されているため、上下の振動が少ないためだと思われます。

ということで、とりあえずはz軸のみを対象とすることにしました。

振動の幅、時間

犬の水飲みを見ていると、1回の水飲みで数秒間飲み続けていますし、その後、しばらくペットボトルも揺れているため、5秒に1回計測することにしました。
振動の幅は、計測結果から、前回の計測結果から0.1より大きい差分があった場合にtweetするようにしました。

連続tweetの防止策

水を飲み始めると5秒毎に連続でtweetしてしまう可能性があるため、閾値以上の揺れを検知したら60秒SLEEPして連続tweetを防止することにしました。

まとめ

とりあえず、上記にて試験運用を開始し、家族不在時も順調に犬の死活監視ができています。
閾値の設定などは改善の余地がありますが、計測データを見ながら、おいおい修正していこうと思います。