３Dプリンターとラズパイで、手動で回せるオートロックを作る

## はじめに
オートロックを作ろうと思ったきっかけですが、友人宅にお邪魔したときのことです。指紋認証だけで鍵の開け閉めができるのを非常にうらやましく思い自宅にも導入したくなりました。また、鍵が上下二つあるタイプだと鍵を二回回さなければいけません、たまに片方だけ閉まっているときなんか、どっちがどっちだかわからなくなり開くまでかなり手間取ることもしばしばあります。オートロックを作れば、こういった問題もすべて解決してくれるのです。
<img width="200" alt="autolock" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3079355/b546cd49-b9de-b494-f74b-8ccf4574c34a.png">

第一弾として自分の部屋にオートロックを作りました。
この時作ったものは、鍵のサムターンに直接サーボモータを取り付けて回すものでした。
<img width="200" alt="sejyou" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3079355/8095213a-1c4b-0306-3f8b-e42cf559320e.jpeg">

オートロック自作の記事を調べると大体このタイプです。作るのは簡単なのですが、モーターに電流が流れているときは回せません。また、万が一外にいるときにラズパイに不具合が起きたら締め出されてしまいます。
部屋ならまだしも、玄関で締め出されてしまったら困ります。
そこで今回は手動でも回せるタイプのオートロックを作りたいと思います。

筆者が今回作成したオートロックは`GitHub`に3Dモデル、基盤データ、プログラム全て公開していますのでご覧ください。

※注意：思い付きで改良や修正を加えているため最新版は異なります、この記事はコミットハッシュ`faf0224`時点のものになります。

https://github.com/takuteh/autolock/tree/main

以下のコマンドをラズパイ上で実行すれば、自動でオートロックが動きます。これの中身について、一部簡略化して説明していきます。
```
git clone https://github.com/takuteh/autolock.git
cd autolock/gear_version
sudo sh setup.sh
```

### 使用するもの
- ラズパイ
- サーボモーター(MG996R)
- 三和電子30mmネジ式ボタン×2(開錠・施錠用)
- リードセンサー（開閉検知）

## 手動でも回せる機構
手動で回す方法ですが、サーボモーターに電流が流れている状態ではとても回すことはできません、仮にできたとしてサーボモーターが壊れる可能性があります。ということで、手動でも回す方法を色々と調べていたところ、素晴らしい機構を考えている人がいました。[こちらの記事](https://dreamerdream.hateblo.jp/entry/2022/09/21/180002)を参考にさせていただきました。

https://dreamerdream.hateblo.jp/entry/2022/09/21/180002

サーボモーターの力をギアを一段かますことによって伝えるということです。

|こんなイメージ↓|この鍵をあける|
|---|---|
|<img width="200" alt="sejyou" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3079355/d97a73a9-be34-62cb-701d-60e477a29a51.jpeg">|<img width="200" alt="sejyou" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3079355/03b53cf3-20d9-7710-65a7-3386d9007a4d.jpeg">


<施錠>
<img width="700" alt="sejyou" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3079355/7ab99efa-9048-15dd-47d3-80d4768c3283.jpeg">

<解錠>
<img width="700" alt="kaijyou" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3079355/d18f2633-cf64-9621-554c-adab99efaa70.jpeg">

図のように施錠・解錠動作を行った後に最初の緑のポジションにギアを戻すことによって手動でも鍵を回す（小さい矢印の方向）ことが出来るようになります。（オレンジ色の部分が鍵です。）

## ハードウェア設計
この仕組みを実際に設計していきます。GitHubの[3D_Models](https://github.com/takuteh/autolock/tree/main/3D_Models)内に作成した３Dモデル(stl)がありますので適宜ご自身の環境に合わせ変更してください。基本的には鍵のギア部分だけ変更すれば良いと思います。
作り方は以下の記事に書きました。

https://qiita.com/takuteh/items/12263e9c711d811a78cb

ちなみに、ギアボックスの固定方法ですが磁石で固定しています。
|<img width="200" alt="sejyou" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3079355/bc0e5f35-3402-5254-9683-b3ee2e0b434e.jpeg">

こちらのマグネットシートがおすすめです。

https://www.askul.co.jp/p/2900985/?sc_e=cp_p_as_go_pl_c_law_c_2900985&utm_source=go&utm_medium=PLA&utm_campaign=PLA_SSC_All&gad_source=1&gclid=CjwKCAjwyJqzBhBaEiwAWDRJVMwiuuhXkT_Al425gHSxu-IIA3tw8F_RpVL-Xea9AwOAKhlW1hv0yBoC2JgQAvD_BwE

## ソフトウェア設計
いよいよ、サーボモーターを動かすソフトを書いていきます。これを作成している時期筆者はC++の勉強をしていたので、基本的にC++で書いています。この記事で紹介するコードは簡略化のためGitHubに載せてるコードとは違いますが、大枠は一緒です。
GPIO制御ライブラリですが以前は[WiringPi](http://wiringpi.com/index.html)が有名でしたが、2019年に開発が中止され現在は非推奨となっているため`pigpio`を使用します。c++で使用する場合は`pigpiod_if2.h`をインクルードします。[ここに](https://negligible.hatenablog.com/entry/2021/03/26/035001#pigpio%E3%83%A9%E3%82%A4%E3%83%96%E3%83%A9%E3%83%AA%E3%81%A8%E3%81%AE%E5%87%BA%E4%BC%9A%E3%81%84)pigpioの話が少し書いてあるので参考にしてください。
実際のコードは[gear_version/src](https://github.com/takuteh/autolock/tree/main/gear_version/src)にあります。

まずは簡単に、回転させてもとに戻すという動作をさせます。usleepの時間ですが、色々と試行錯誤した結果この秒数が最も早くかつ安定して動きました。
```control_servo.cpp
#include<pigpiod_if2.h>
#include<unistd.h>

#define SERVO_PIN 19

int main(){

int pi = pigpio_start(nullptr, nullptr);
//1500は初期位置
set_servo_pulsewidth(pi,SERVO_PIN,1500);
usleep(100000);
//2500で開錠位置まで回転
set_servo_pulsewidth(pi,SERVO_PIN,2500);
//500で施錠位置まで回転
//set_servo_pulsewidth(pi,SERVO_PIN,500);
usleep(800000);
//初期位置に戻す
set_servo_pulsewidth(pi,SERVO_PIN,1500);
 
return 0;
}
```
コードが書けたら`pigpiod_if2`ライブラリをリンクしてコンパイルします。
```
g++ -o control_servo control_servo.cpp -lpigpiod_if2
```
ボタンで開錠・施錠、ドアが閉まったことを検知するリードセンサーで施錠するコードを追加していきます。
リードセンサーが反応していない間、つまりドアが空いている時は施錠ボタンを押しても施錠しない。鍵が開けっぱなしになることを防ぐため、開錠ボタンを押してから数分後リードセンサーに反応があったら施錠するという動作にさせています。
各ボタンの抵抗も設定したのですが、どうしてもチャタリングが発生してしまうため、一度入力があったら一定時間は入力を無視するという処理を入れています。
```control_servo.cpp
#include<pigpiod_if2.h>
#include<iostream>
#include<ctime>
#include<unistd.h>

#define SERVO_PIN 19
#define OP_SW 9
#define CL_SW 2
#define RE_SW 3
#define DEBOUNCE_TIME_US 1.5 
#define TIMER_INTERVAL 300

int pi = pigpio_start(nullptr, nullptr); //pigpio初期化

std::time_t last_button_time=0; //最後にボタンが押された時刻
std::time_t current_time=0; //ボタンが押されたときの現在時刻
std::time_t start_rsw_time=0; //リードセンサーの初期時刻
std::time_t current_rsw_time=0; //リードセンサーの現在時刻

void open(){ //開錠
set_servo_pulsewidth(pi,SERVO_PIN,1500);
usleep(100000);
set_servo_pulsewidth(pi,SERVO_PIN,500);
usleep(800000);
set_servo_pulsewidth(pi,SERVO_PIN,1500);
}

void close(){ //施錠
set_servo_pulsewidth(pi,SERVO_PIN,1500);
usleep(100000);
set_servo_pulsewidth(pi,SERVO_PIN,2500);
usleep(800000);
set_servo_pulsewidth(pi,SERVO_PIN,1500);
}

void open_switch(int pi, unsigned gpio, unsigned level, uint32_t tick){
    //現在時刻を取得
    current_time = time_time();
    // 直前のボタンの押下から一定時間が経過していない場合は無視する
    if (current_time - last_button_time < DEBOUNCE_TIME_US) {
        return;
    }
    if(level==1){ //開錠ボタンに入力があったら
        //ここからリードセンサーのタイマーを設定する
        start_rsw_time = time_time();
        std::cout<<"open"<<std::endl;
        open();
    }
    // 最後のボタン押下時刻を更新
    last_button_time = current_time;
}

void close_switch(int pi, unsigned gpio, unsigned level, uint32_t tick){
    current_time = time_time();
    if (current_time - last_button_time < DEBOUNCE_TIME_US) {
        return;
    }
    if(level==1){ //施錠ボタンに入力があったら
        //リードセンサーに入力がなかったら無視する
        if(gpio_read(pi,RE_SW)==0){ 
            return;
        } 
        std::cout<<"close"<<std::endl;
        close();
    }
    last_button_time = current_time;
}

void read_switch(int pi, unsigned gpio, unsigned level, uint32_t tick){
     current_time = time_time();
     if (current_time - last_button_time < DEBOUNCE_TIME_US) {
        return;
     }
    if(level==1){ //リードセンサーに反応があったら
        std::cout<<"read_sw"<<std::endl;
        sleep(2); //完全に扉が閉まるまでを考慮し2秒待つ
        close();
    }
    last_button_time = current_time;
}

int main(){
        //GPIOを読み取りモードに設定
        set_mode(pi, OP_SW, PI_INPUT);
        set_mode(pi, CL_SW, PI_INPUT);
        set_mode(pi, RE_SW, PI_INPUT);

        //各ボタンのプルアップ・ダウン抵抗設定
        set_pull_up_down(pi, OP_SW, PI_PUD_UP);
        set_pull_up_down(pi, CL_SW, PI_PUD_UP);
        set_pull_up_down(pi, RE_SW, PI_PUD_UP);

        //ボタン割り込みのコールバック関数設定
        callback(pi, OP_SW, RISING_EDGE, open_switch);
        callback(pi, CL_SW, RISING_EDGE, close_switch);
        callback(pi, RE_SW, EITHER_EDGE, read_switch);

        while(1){
            current_rsw_time=time_time();
            auto elapsed_rsw_time=current_rsw_time-start_rsw_time;
            //現在時刻とリードセンサーの初期時刻の差がTIMER_INTERVAL以上で+0.5秒以内になったら
            if(elapsed_rsw_time>=TIMER_INTERVAL&&elapsed_rsw_time<=TIMER_INTERVAL+0.5){
              if(gpio_read(pi,RE_SW)==1){ //リードセンサーの反応があったら施錠
               std::cout<<"door_closed"<<std::endl; 
               close();
             }
            }
            sleep(1);
            std::cout<<"0"<<std::endl;
        }
        return 0;
        }
```
ここまでできたら、とりあえず鍵の開け閉めができるようになります。

<iframe width="100" height="200" src="https://www.youtube.com/embed/FGpgZc9tSx0" title="鍵の開け閉めをする" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>

これでは内部からしかスマートロックが使えないので、外からでも開錠できるようにします。ICカードを読み取る方法や指紋認証などをさせても良いのですが、玄関に取り付けるのでセキュリティ面や見栄え的にも外側に何かを設置するということはしたくありません。そのため今回はスマホから通信で開錠させることにします。通信方法は実装が比較的容易な`MQTT`を使用します。ちなみにICカードを読み取る方法ですが、実は第一弾の部屋のロックには実装しており、GitHubの[old_version](https://github.com/takuteh/autolock/tree/main/old_version)にありますので参考までに。


MQTT実装まで書くと長くなってしまうので、以下の記事にMQTT実装編を載せてます。

https://qiita.com/takuteh/items/b1895481f542f40db49f

## セットアップ
ここからはおまけですが、作ったプログラムがラズパイ起動時に自動で実行されるようにする。MQTTの設定やコンパイルなども楽にしたいので以下の3本立てでセットアップをできるようにします。

**1\. プログラムをOS起動時に自動実行させる**
→systemdのunitファイルを作成し、サービスを登録し自動起動するようにします。
**2\. プログラムをmakeでコンパイルする**
→Makefileを作りautolock.cppを簡単にコンパイルできるようにします。
**3\. 各種設定を実行するスクリプトの作成をする**
→1,2の作業を自動で実行し、MQTTの設定ファイルの編集、オートロックプログラムの設定ファイルの編集など、プログラムを動かすまでに必要な作業をすべて自動で実行してくれるシェルスクリプトの作成を行います。

https://qiita.com/takuteh/items/81c2b103b154cb435352