14
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Raspberry PiAdvent Calendar 2021

Day 12

ラズパイでドアの開閉を検知して一人暮らしの家族を見守る

Posted at

概要

Raspberry Piとリードスイッチを使い、引き戸の開閉を検知するようにしました。
開閉状態をグラフ化することで、離れて暮らす家族の活動状況や睡眠状況などを把握することができます。
既存のドア開閉検知のガジェットと比較して、RaspberryPiで作成することの利点として、簡単にグラフ化して分析できること、自由度の高くカスタマイズ可能な物が作れることなどがあります。

こんな感じで分析できます
ばあちゃんドア解析.jpg

ドアの開閉検知について

ドアの開閉検知には、磁力式のリードスイッチを使いました。
秋月電子で1個250円でした。

image.png

片方に2本の導線がついており、もう片方には線はついておらず、磁石が入っています。
磁石が近くにないときには、2本の導線は導通していませんが、磁石が近くになると導通するようになります。

image.png

このリードスイッチをドアの上にこのように両面テープで貼り付け設置しました。
リードスイッチからラズパイまでの距離が遠く、リードスイッチについている導線では長さが足りなかったので
導線を追加で購入し、距離を延長して設置しました。

2021-12-11 21.46.00.jpg

そして、2本の線をそれぞれ、ラズパイのGNDとGPIOに接続します。
私は参考にさせていただいたこちらのサイトに倣って、以下のようにGPIO18に接続しました。

image.png

ドアの開閉がきちんと検知できるかどうかの動作検証も、参考サイト様のコードを使わせてもらいました。

door_test.py
import time
import RPi.GPIO as GPIO

GPIO.setmode(GPIO.BCM)

#GPIO18pinを入力モードとし、pull up設定とします
GPIO.setup(18,GPIO.IN,pull_up_down=GPIO.PUD_UP)

sw_status = 1

while True:
    try:
        sw_status = GPIO.input(18)
        if sw_status == 0:
            print("Close")
        else:
            print("Open!")

        time.sleep(0.03)
    except:
        break

GPIO.cleanup()
print("end")

これを実行させ、ドアを開け閉めすると、うまく行っていればCloseとOpen!の表示が変わるはずです。

$ python3 door_test.py
Close
Close
Close
Close
Close
Open!
Open!
Open!
Open!
Open!

データを可視化する

ドア開閉データの可視化には、お手軽かつ無料で使えるambientというIoT可視化サービスを利用します。
マイコンやlinuxのライブラリが揃っていて、データを送りつけるだけで簡単に可視化してくれる便利なサービスです。

ユーザー登録して、チャンネルを作成します。ここでは割愛します。
すると、チャンネル一覧のページから、チャンネルIDとライトキーが見られます。
こちらは使いますので、どこかにコピーしておきましょう。

ラズパイでの作業です。Ambientのpython3のライブラリをpip3でインストールします。

$ pip3 install git+https://github.com/AmbientDataInc/ambient-python-lib.git

これでpython上でambientを使えるようになりました。

ドアの開閉状況をambientに定期的に送るプログラムを書きます。
5分間で戸が何回開けられたのかを記録し、5分ごとに送るようにしています。
ドアが開けられたという判定は、毎秒ドアの開閉状況を監視し、3秒以上開けている状態が持続したときに
カウントを+1しています。

door.py
import time
import RPi.GPIO as GPIO
import ambient

GPIO.setmode(GPIO.BCM)

GPIO.setup(18,GPIO.IN,pull_up_down=GPIO.PUD_UP)

ambi = ambient.Ambient(12345, "abcdefghijklmn")

sw_status = 1
open_count = 0
tmp_open_count = 0

while True:
    try:
        sw_status = GPIO.input(18)
        if sw_status == 0:
            #close
            tmp_open_count = 0
        else:
            #open
            tmp_open_count += 1
            if (tmp_open_count ==3):
                open_count += 1
                print("open")

    except:
        print("error")

    if int(time.time()) % 300 == 0 :
        r = ambi.send({"d1": open_count})
        open_count = 0

    time.sleep(1)

GPIO.cleanup()
print("end")

9行目の

ambi = ambient.Ambient(12345, "abcdefghijklmn")

は先ほど作成したチャンネルのIDとライトキーに変更してください。

このプログラムを実行すると、基本的にずっとデータを送り続けてくれますが、
何かの理由でプログラムが停止してしまう可能性があります。
ですので、systemdにservice登録して、プログラムが終了しても、ラズパイが再起動しても、プログラムが自動で再起動するようにします。

$ sudo vi /etc/systemd/system/door_open.service

で下記のサービス内容を記述します。

door_open.service
[Unit]
Description=door open data send
[Service]
Type=simple
ExecStart=/usr/bin/python3 /home/pi/work/door.py
Restart=always

[Install]
WantedBy=multi-user.target

サービスを有効化し、起動します。

$ sudo systemctl enable door_open.service
$ sudo systemctl start door_open.service

ちゃんと動いているか確認します。
active (running)になっていれば、サービスが正常に動いているはずです。

$ sudo systemctl status door_open.service
● door_open.service - door open data send
   Loaded: loaded (/etc/systemd/system/door_open.service; enabled; vendor preset: enabled)
   Active: active (running) since Fri 2021-12-10 20:10:07 GMT; 18h ago
 Main PID: 15780 (python3)
   CGroup: /system.slice/door_open.service
           └─15780 /usr/bin/python3 /home/pi/work/door.py

Dec 10 20:10:07 raspi-ima systemd[1]: Started door open data send.

正常にAmbientにデータ送信がなされていれば、Ambient上のグラフでデータがきていることが確認できます。
来ていなければ、チャンネルIDやライトキーに誤りがないか確認しましょう。

一日ほどデータを取り続けると、このようなグラフが見えます。

スクリーンショット 2021-12-11 23.45.58.png

分析してみよう

無事データが取れたら、グラフを見て分析しましょう。

今まで取っていたドアの開閉のデータですが、周辺情報を簡単に述べますと
祖母が一人暮らしをしており、祖母が寝たりする一番いる事が多い居間の引き戸にセンサーを設置しています。
祖母は以前から、寝ても途中で起きてしまい、そこからなかなか眠れないという悩みを抱えていました。
そういった情報を基にグラフを眺めると、このような分析ができます。

ばあちゃんドア解析.jpg

他の日を見ても、2~4時に一度起きている事が分かりました。
祖母は睡眠薬を処方されていますが、あまり飲みたくないらしく、飲んだり飲まなかったりしているようです。
睡眠の状態は健康に大きく関わってくるので、これ以上睡眠状態が悪化するようなら、きちんと毎日睡眠薬を飲むように言わないといけないと思っています。

ちなみにですが、ドアの開閉だけではなく、照度や気温湿度、二酸化炭素濃度などのデータも撮っていますので、
部屋にいるのかいないのか、昼寝をしているのかなど、総合的に分析することができます

スクリーンショット 2021-12-12 0.28.11.png

LINEで非常時の通知をしよう

データ分析ができることはとても良いのですが、いつもこのデータを確認するのは大変です。
ですので、いつもと異なる状況が起こったときに、LINEで通知するようにしましょう。

祖母は朝は6時ごろにはいつも起きているようですが、もし体調が悪くて朝起きれていない状態になった場合
LINEで通知することにしましょう。

LINE Notifyという、LINEアカウントさえ持っていれば簡単に通知を送る事ができるサービスがあります。
以下のページを参考にして、通知を送るためのトークンを発行します

トークンが発行できたら、上のページにもあるように、curlでテストしてみることをお勧めします。

トークンでうまくLINE通知が送れる事が確認できましたら、先ほどのプログラムを改良します。
4時から8時の間にドアの開閉がない場合、通知を送るようにします。

door_notify.py
import time
import RPi.GPIO as GPIO
import ambient
import requests
from datetime import date, timedelta, timezone
import datetime

GPIO.setmode(GPIO.BCM)
GPIO.setup(18,GPIO.IN,pull_up_down=GPIO.PUD_UP)

ambi = ambient.Ambient(12345, "abcdefghijklmn") ## 置き換えること

JST = timezone(timedelta(hours=+9), 'JST')

sw_status = 1
open_count = 0
tmp_open_count = 0

range_start = datetime.time(4, 0, 0)  # 4時なら4,0,0
range_end = datetime.time(8, 0 , 0)
range_count = 0
range_end_1sec_after =  (datetime.datetime.combine(date.today(),range_end) + timedelta(seconds=1)).time()

def line_notify():
    payload = {'message':'4時から8時までドアが一度も開かれていません。'}
    headers = {'Authorization': 'Bearer ' + 'token'} # tokenを置き換えること!
    requests.post('https://notify-api.line.me/api/notify', data=payload, headers=headers)

def time_in_range(start, end, x):
    """start とendの時刻の間に、xが入るか判定する関数"""
    if start <= end:
        return start <= x <= end
    else:
        return start <= x or x <= end

while True:
    try:
        sw_status = GPIO.input(18)
        if sw_status == 0:
            #close
            tmp_open_count = 0
        else:
            #open
            tmp_open_count += 1
            if (tmp_open_count ==3):
                open_count += 1
                print("open")
                if(time_in_range(range_start, range_end, datetime.datetime.now(JST).time())):
                    range_count += 1

    except:
        print("error")

    if int(time.time()) % 300 == 0 :
        r = ambi.send({"d1": open_count})
        open_count = 0

    # 現在時刻が範囲終了時刻なら、LINE通知を送るか判断する
    if(time_in_range(range_end, range_end_1sec_after, datetime.datetime.now(JST).time())):
        if(range_count ==0):
            line_notify()
        range_count = 0

    time.sleep(1)

GPIO.cleanup()
print("end")

line_notifyの関数のトークンは、先ほど発行したトークンに置き換えてください。

ざっくり解説すると、4時から8時までのドア開閉回数をrange_countという変数でカウントし
8時00分00秒〜8時00分01秒のときに判断し、ドア開閉がない場合はLINE通知し、そうでないならカウントを0にします

開閉がなかった場合、このようにLINEで送られます。
2021-12-12 02.13.11.jpg

これで朝に倒れていた場合、早期に気づくことができると思います。

最後に

データが取得さえできれば、他にも色々応用できると思います。
機械学習を使って、異常検知とかできればもっと良いのでしょう。勉強します。

14
9
10

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?