ラズパイとpasoriを使ってブラウザで見れる簡単な入退室管理システムを作ってみた


#はじめに
前回書いた「[ブラウザでnfcpyを使ってNFCタグのIDmを表示してみる](https://qiita.com/mono_taro/items/438e1d5f34f2893c642e)」と前々回書いた「[PHPでデータベースを使ってみるための備忘録](https://qiita.com/mono_taro/items/aa8265855a7e128e6f1d)」を組み合わせて簡単な入退室管理システムを作ってみました。

github:https://github.com/mono-taro/AceessControl

####動機
以前から、大学のサークル/部活動などで部室は開いてるのか、誰がいま部室にいるのかわかることの出来るシステムがあったらいいなぁと思ってので、今回PHPの勉強を兼ねて作りました。
最初は在否を手動で登録する方法を考えたのですが、手間が多くてめんどくさそうなので、誰でも持っているような交通系ICカードのようなNFCカードのIDmでメンバーを特定して自動で在否を更新する方式にしました。これならリーダーにカードをかざすだけなので改札機通るのと同じ手間で済むのでそこまで面倒ではないと思います。

##作品紹介
PCで制作してラズパイに実装し、同じLAN内のPCやスマホで動作を確認しています。
最初はHTMLのみのクソデザインだったのですが、非常に見た目が悪かったので、Bootstrapで少し見た目を良くしてみました。CSS自体初めてだったので右も左もわからない状態でとりあえず使ってみたので、稚拙な中身かもしれません…

以下canvaで作ったかなり大雑把な、なんちゃってサイトマップ？フローチャート？的なものです
![Site Map (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/584794/269a20f1-9721-f221-0ef3-ab432bce486c.png)
![Site Map続き (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/584794/f77a728e-016f-76d9-5ba9-86078ef9df87.png)

要件定義は特にせず、当初はIDm読み取り画面とメンバー追加等の編集画面のみ作るつもりでほぼ見切り発車状態で作り始めましたが、作ってる途中で管理者ページ、管理者ログイン、管理者編集、メンバー一覧等が必要だと思い、あとから付け足して作ってます。

なお、データーベースは前々回作成したものを管理者用に流用し、追加で、前々回作ったものにboolを付け加えただけのような構成のものを新たにメンバー用として作成し、使用しています。
![Screenshot from 2020-04-15 12-09-13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/584794/b9182bcf-ed87-127b-08a4-3cd173a69a52.png)

あれこれ見様見真似で作り、継ぎ接ぎだらけの駄作ではありますが、ご笑覧頂けたら幸いです…


###各ページ紹介
各ページの大雑把な紹介とカードを読み取るトップページの処理の部分を紹介します。

####カードIDmの読み取りトップページ
![Peek 2020-04-14 21-52.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/584794/c72d3d94-d654-3f99-5b35-8b7ee014222c.gif)
ページを読み込んだら自動で待機状態になり、pasoriが接続されていなければ接続を促すメッセージが表示されます。
カードのIDmを取得し、登録されているメンバーのカードのIDmであれば状態(在否)を取得し状態によってメッセージを表示し、状態を更新します。登録されていないIDmであれば別ページに移動し登録するか確認します。
あと、入退場音がほしかったのでWindows2000以降のログオン/オフ音を再生する機能と、LINEAPIを使ってbotからLINEで通知が来る仕組みも実装しています。(こちらを参考にさせていただきました。[LINEボットを作る(プッシュ編)](https://imoni.net/tips/00xx/0012.html))
※現在時刻が表示されていますが、打刻機能はないので特に意味はありません…


この画面の処理の大体の部分は前回書いた「[ブラウザでnfcpyを使ってNFCタグのIDmを表示してみる](https://qiita.com/mono_taro/items/438e1d5f34f2893c642e)」と前々回書いた「[PHPでデータベースを使ってみるための備忘録](https://qiita.com/mono_taro/items/aa8265855a7e128e6f1d)」の内容を流用しています。


```js:top.html
<script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
  <script>

  //非同期処理
  $(function(){
      
    //ページを読み込んだら自動で処理開始
    $(document).ready( function(){

      $.ajax({
        url: " touch.php",
        dataType : "text",
        async: true,
      }).done(function(data){
        console.log(data + "を取得しました。");
        
        //<div id="result">にdataを代入
        $("#result").html(data);
        //5秒後にページ更新
         setTimeout(function () {
            location.reload();
          }, "5000");
      })
    });

  });
```
```php:touch.php
//データーベース接続用の設定ファイルを読み込み
require( __DIR__ . '/connect.php');
$dbh = connectDB(); 

//LineAPIの設定ファイルを読み込み
require( __DIR__ . '/line_bot.php');

$idm=0;

//IDmの取得
$command="  echo 'sudo権限のパスワード' | sudo -S  python3 nf.py";   //IDm読み取りのためのPython呼び出し
exec($command,$output);
$idm=$output[0];                                        //呼び出したPythonから返ってきた内容を$idmに代入
if($idm!=0){
    //サニタイズ処理
    $idm=htmlspecialchars($idm,ENT_QUOTES,'UTF-8');
     


    //データーベースに一致するメンバーのIDmの検索
    $sql='SELECT name,bool FROM member WHERE idm=?';   //SQL命令文 入れたいデータは「？」
    $stmt=$dbh->prepare($sql);                         //準備する命令
    $data[]=$idm;                                      //「?」にセットしたデータの書き出し
    $stmt->execute($data);                             //クエリの実行
    $rec=$stmt->fetch(PDO::FETCH_ASSOC);
    $name=$rec['name'];                                //一致したメンバー名前の代入
    $bool=$rec['bool'];                                //メンバーの状態(在室かどうか)を取得

    $stmt=null;                                        //データーベース切断
    $data=array();                                     //書き出し用のデータ初期化

    //データーベースに一致するメンバーがいない場合
    if($rec==false){
        print '<meta http-equiv="refresh" content=" 0; url=touch_error.html">';         //メンバーを追加するか確認するtouch_error.htmlに自動で遷移
        exit();
    }
  
    //bool=0不在,bool=1在室

    //不在だった場合
    if($bool==0){
        //bool値の更新
        $sql='UPDATE member SET bool=? WHERE idm=?';
        $stmt=$dbh->prepare($sql);
        $data[]=1;                                          //在室に更新
        $data[]=$idm;          
        $stmt->execute($data);  

        //再生する1~3までの入場音をランダムで決定
        switch(rand(1,3)){                                      
            case 1:
                $music="win2000.mp3";
            break;

            case 2:
                $music="winxp.mp3";
            break;

            case 3:
                $music="win7.mp3";
            break;
        }

        
        //top.htmlにhtmlタグを送る
        print '<center><h1>こんにちは、'.$name.'さん。</h1></center>
            <audio src='.$music.' autoplay></audio>';               //mp3再生タグ 

        //line_botを呼び出す
        $mes=$name."さんが入室しました。";                          //Lineに送るメッセージ
        line($mes);
    
    //在室だった場合
    }else if($bool==1){
        $sql='UPDATE member SET bool=? WHERE idm=?';
        $stmt=$dbh->prepare($sql);
        $data[]=0;                                                  //不在に更新
        $data[]=$idm;          
        $stmt->execute($data);  
        

        //ランダムで退場音を決定
        switch(rand(1,3)){
            case 1:
                $music="win2000off.mp3";

            break;

            case 2:
                $music="winxpoff.mp3";

            break;
            
            case 3:
                $music="7off.mp3";
            break;
        }

        //top.htmlにhtmlタグを送る
        print '<center><h1>お疲れ様でした、'.$name.'さん。</h1></center>
        <audio src='.$music.' autoplay></audio>';

        //Lineに送るメッセージ
        $mes=$name."さんが退室しました。";
        line($mes);

        }
        
        exit();
}else{
    //idm取得失敗 USBが接続されていない/権限が付与されていない可能性あり  
    print '<div class="alert alert-info" role="alert"><center><h4>リーダーの接続を確認してください</h4></center>    </div>';
    exit();
} 
```
```python:nf.py
import nfc
import binascii
from nfc.clf import RemoteTarget
 
import hashlib


try: 
    #USB接続
    clf = nfc.ContactlessFrontend('usb')            #USBが接続されていない/接続不良の場合ここでエラーが起きる
    tag = clf.connect(rdwr={
        'on-connect': lambda tag: False
    })
    idm = binascii.hexlify(tag.idm)                 #取得した値を16進数に変換
    idm = hashlib.md5(idm).hexdigest()              #16進数に変換した値をMD5でハッシュ化                                                        
    print(idm)                                      #ハッシュ化した値を返却
    exit()


    
    
except AttributeError:
    print("error")
    exit()
```
top.htmlとtouch.phpが同時に読み込まれ、pythonのnf.pyが待機状態になります。nf.pyでカードのIDmを取得しMD5でハッシュ化しtouch.phpへ返し、既に登録されているカードか確認し、登録されているのであればカードのメンバーの状態(bool=0であれば不在、bool=1であれば在室)を取得し、状態を更新し、それぞれの処理行いHTMLタグのメッセージをtop.htmlに返しています。

####メンバーの在否状況確認ページ
![Screenshot from 2020-04-14 20-15-09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/584794/c2ea1138-49b1-cf6a-ac21-a87fd3fce9c3.png)
このような感じで在室かどうかを表示しています。コードはメンバーの修正や削除に使う主キーなので、ここではあまり意味ないです。

####管理者ページ・管理者管理ページ
管理者ログインと管理者の管理を一通り行っています。
なお、管理者コード1の管理者(この場合mono)は削除出来ないようになっています。
![Peek 2020-04-15 18-33.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/584794/925d5aff-bf7f-6de8-c017-b19caf3fcc7f.gif)

###メンバーの管理
メンバーの管理を一通り行っています。
メンバー追加/修正時に登録するカードを読み取った際、既に登録してあるカードのIDmの場合は登録できないようになっています。
![Peek 2020-04-15 18-38.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/584794/0d62c6cc-6cd4-c681-6200-297d18f3feec.gif)



ちなみにスマホでのメンバー在火状況確認画面はこんな感じです。
![Screenshot_20200414-180212.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/584794/19eef1c5-dd12-3b2a-0f1b-377453537298.jpeg)



詳しいコードの内容は[こちら](https://github.com/mono-taro/AceessControl/)においておきます。

##最後に
一応それっぽい入退出管理システムを作ることが出来、また、勉強の成果としてとりあえず何か使えるものを作ってみたかったのでひとまず満足です。たぶんこの制作を通して大体のPHPの基礎的なことは出来たのではないかなと思います。
課題点としてデザインがおかしかったり、複数端末でIDm読み取りトップページを表示していると同期にずれが起きが発生するという問題点があるので、解決できるように勉強していきたいです。

もし改善点等がありましたら、大変勉強になりますし自分では見えてないことが見えてくるようになると思いますので、ご教示いただけると幸いです。

##参考文献
谷藤賢一(2017)「気づけばプロ並みPHP 改訂版--ゼロから作れる人になる!」
[LINEボットを作る(プッシュ編)](https://imoni.net/tips/00xx/0012.html)
[【PHP・MySQL】データベースに画像ファイルを保存・表示する方法 ](https://codeforfun.jp/save-images-php-and-mysql-1/)