オートロックを自動解除したい[ラズパイ]

Qiita初投稿です。今回自宅のスマートホーム化を目指してオートロックのあるマンションでも鍵を取り出さずに通過できるようにしてみました。
既にだいたい同じ方法で実践されている先人様を参考に自分に合わせたものを構築できました。

## はじめに

まず今回私がやりたいこと、やったことを図に書いてみました。
![kousei.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/996214/dcd37799-b3a9-87fa-14d4-a808fe84bc64.png)

スマートホームの一つとして、スマホを鍵代わりにするスマートロックがあると思います。
一々鍵を出す必要がなく、鍵の閉め忘れを防ぐことができ、お出かけのトラブルを減らせるのが魅力なのですが下記の問題点があります。

**オートロックマンションではエントランスで鍵を出さないといけない**

スマートロックは自宅の扉の鍵をスマホで制御できるようにするものなのでオートロックについては為す術がないです。
私は鍵を忘れやすいし失くしやすい、掛けたかどうかも忘れる、疲れてるときに一々鍵を取り出したくない、という事情がありGoogle先生に何とかならないか相談しました。
そして先人様も同じ課題に取り組んでおり、これを真似すれば何とかなるかもと思い挑戦してみました。

これから図に記載した番号順に実装内容について説明します。

## 各実装内容

### ①帰宅

まず帰宅の検知をして、その情報をラズパイに送ります。
ややこしいので先に説明すると、今回作成したNode-REDフローでは二つのステータスを扱います。
各ステータスと判断方法が以下となります。

* 在宅：IFTTTによる位置情報から自宅付近にいることを判断
* 在室：ラズパイからスマホに対してPingを飛ばして帰宅者が部屋内にいることを判断

上記二つのステータスの条件が満たされたときのみエントランスドアの操作を行います。
このステータスは誤動作を防ぐことを目的としており、家主が不在時にインターホンが鳴らされてもエントランスドアが開かないようにし、逆に家主が在室している時にインターホンが鳴らされてもエントランスドアを勝手に開けないようにします。

①帰宅のシーケンスでは在宅のフラグが立ち、それをラズパイに伝えて在宅変数の更新を行います。
外出と帰宅の検知はIFTTTで位置情報をトリガーにしたアプレットで行います。
設定内容は先人様のまねっこです。参考までに以下に画像を載せます。

![IFTTT_setting.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/996214/7e546a51-2cbe-ea02-80cc-1979e2fcf776.png)
参考：https://qiita.com/minatomirai21/items/4c4e777b43ede1e42900

### ②自宅付近に来たことを通知

先ほどは帰宅の検知まででしたが、IFTTTから直にラズパイに通知するわけでなく、BeeBotteを介しています。
![BeeBotte_settings.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/996214/2a696d1c-bc71-89e2-be13-4d662f638d76.png)
参考：https://qiita.com/minatomirai21/items/4c4e777b43ede1e42900

### ③自宅付近にいるフラグを立てる

以下はBeeBotteからの通知を受け取るNode-REDフローです。
![Node-RED_在宅フラグ制御.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/996214/9fd3f8b2-2e19-2979-6733-39d50cacac94.png)
MQTTノードでBeeBotteからの通知を受け取り、在宅のグローバル変数を更新しています。

### ④在室確認(Pingでスマホ有無確認)

これは実際には1分ごとにラズパイからPingを飛ばし続けているので必ずこの順序で行っているわけではないです。説明しやすさのために番号を振っています。
ラズパイから1分間隔でPingを飛ばして、応答があれば在室フラグを立てます。
![Node-RED_在室フラグ制御.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/996214/b6577051-3798-9d86-9f86-0fe725d744e8.png)

これに関してはもっとスマートな方法があるかとは思うのですが、自分では思いつきませんでした。
Pingによる負荷がどれほどのものかはわからないのですが、できればスマホの電池を節約するために、Wi-Fiルータの設定ページのスクレイピングによってスマホが接続されているかどうかを確認できるようにすることも考えてはいます。いつかそのうち。

### ⑤インターホンの音を検知

これについてはNode-REDはPythonスクリプトを動かして検知信号を受け取っているだけなのでフローの画像は省きます。
以下がコードです。

```python
import pyaudio
import numpy as np

CHUNK = 1024
FORMAT = pyaudio.paInt16
CHANNELS = 1
RATE = 44100
RECORD_SECONDS = 210

THLESHOLD = 0.8

p = pyaudio.PyAudio()

stream = p.open(format=FORMAT,
                channels=CHANNELS,
                rate=RATE,
                input=True,
                input_device_index=2,
                frames_per_buffer=CHUNK)

while stream.is_active():
    try:
        input = stream.read(CHUNK, exception_on_overflow=False)
        ndarray = np.frombuffer(input, dtype='int16')/32768.0

        #print(ndarray)
        if THLESHOLD < ndarray.max():
            print("DETECTED!!!")

    except KeyboardInterrupt:
        break

print('Start Streaming')

stream.stop_stream()
stream.close()
p.terminate()

print('Stop Streaming')
```
参考：https://qiita.com/mix_dvd/items/dc53926b83a9529876f7
このスクリプトでは録音などはせず、一定以上の音量をNode-REDに通知し続けるものとなっています。（print("DETECTED!!!")がNode-REDで取り扱われる。）

### ⑥モーターで開閉ボタンを押す

インターホンの音を検知した時に以下のフローを動かします。
![Node-RED_モータ制御.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/996214/6540b402-fc9e-61a9-0e2d-ae9f6d11a861.png)

delayノードで5秒に一回のみメッセージを送信し、その間のメッセージはすべて破棄しています。今回私が用意したスクリプトの作りでは音が閾値を超えている間、メッセージが飛び続けているのでこれを設けています。
そしてここでもPythonスクリプトを動かしています。ラズパイのGPIO制御のノードがNode-REDでは用意されていましたが、それでは思った通りのモータ制御ができなかったので。

```python
import RPi.GPIO as GPIO
import time

class SG90_92R_Class:
    def __init__(self, Pin, ZeroOffsetDuty):
        self.mPin = Pin
        self.m_ZeroOffsetDuty = ZeroOffsetDuty

        GPIO.setup(self.mPin, GPIO.OUT)
        self.mPwm = GPIO.PWM(self.mPin , 50) # set 20ms / 50 Hz

    def SetPos(self,pos):
        #Duty ratio = 2.5%?12.0% : 0.5ms?2.4ms : 0 ～ 180deg
        duty = (12-2.5)/180*pos+2.5 + self.m_ZeroOffsetDuty
        self.mPwm.start(duty)

    def Cleanup(self):
        self.SetPos(90)
        time.sleep(1)
        GPIO.setup(self.mPin, GPIO.IN)

if __name__ == '__main__':
    #Useing GPIO No.  to idetify channel
    GPIO.setmode(GPIO.BCM)
    Servo = SG90_92R_Class(Pin=4,ZeroOffsetDuty=0)
    try:
        Servo.SetPos(60)
        time.sleep(1)
        Servo.SetPos(115)
        time.sleep(1)
        Servo.SetPos(90)
        time.sleep(1)
    except KeyboardInterrupt  :
        print("\nCtl+C")
    except Exception as e:
        print(str(e))
    finally:
        Servo.Cleanup()
        GPIO.cleanup()
        print("\nexit servo script")
```
参考：https://sites.google.com/site/memorandumjavaandalgorithm/raspberry-pi-jiang-zuo7-servo-sg90-sg92
マイクもモータもほとんどコピペです。先人様ありがとうございます。
スクリプト起動時にインターホンの通話ボタン、次に解錠ボタンを押すように回転を組んでいます。

![PXL_20210101_134533081.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/996214/1dce28c5-d613-1c9c-a3c9-073f476aa0ed.jpeg)

### ⑦ドアが開く

モータにより解錠ボタンが押されることでドアが開きます。

通話ボタンがボタンの端を押しているせいか時々不発になりますが、それ以外は今のところ問題なく開錠できます。

## 使用したもの

今更ですが使用したものをまとめます。

* RaspberryPi4
* SG92R（モータ）
* Htwon製PC用USBマイク
* CM3PL（モータやラズパイ固定用テープ）

## 感想や今後

書きなぐりでしたが初のQiita投稿を達成できて満足です。
今後時間をかけてテストし続けて特に問題なければ我が家でもスマートロックを導入しようと思います。

かなり作りは荒いですが自分の満足できるものが作れてよかったです。今回作ったものの改良はもちろん、またこういった面白い工作をしていきたいです。
読んでいただきありがとうございました。参考になれば幸いです。