見出し画像

RaspberryPi+webixで玄関鍵を管理

今回は、RaspberryPi zero w2に電子錠を制御する回路と実装し、スマホから施錠操作や状態を確認するWebアプリをWebixで実装してみましたので、紹介します。
最近は、マンションなどでは電子錠とスマホが連携したサービスも多く発表されていますが、今回は、持ち家で同じようなスマホで玄関の鍵の制御や状態確認をする回路とアプリを実装しましたので紹介します。玄関の鍵が電子錠になっていることが前提になっています。通常は、専用のリモコンキーを使って、施錠や解錠をしますが、この事例では、同じような操作をスマホからできるようにしたことや、現状の施錠状態を遠隔からスマホで確認できることや、10分以上解錠していると、メールで通知してくれるなどの応用もできる事例です。
家には、室内から鍵の状態を確認したり、施錠・解錠できる押しボタンがあります。

施錠時には、LEDが点灯し、解錠時には、LEDが消灯するボタンです。ボタンは、施錠と解錠が同じボタンなので、2回押下すると、元の状態に戻ってしまいます。(自宅内外で解錠しても、一定時間内にドアを開けないと、自動的に施錠する仕組みになっています。)
しかし、外出先からスマホで操作するときには、現状の状態を把握できることや、きちんと施錠状態にする制御が必要でボタン操作だけでは、管理できません。現状の施錠状態も確認できる機能が必要です。
今回は、検討した結果、LEDの点灯状態を確認して、施錠・解錠を判断することにしました。LED点灯時には、LED端子の電圧が、0V付近になり、消灯時には、17Vになることをテスタで調査し、RaspberryPiで測定できる3.3V以下の電圧になるように電圧を抵抗で分割し、電圧測定するロジックとしました。抵抗22Kオームと3.3Kオームで分割すると、2V付近の電圧になるので、その電圧をINA219センサで測定し、2V以上のときは、消灯 2V以下だと点灯と判断するようにしました。
鍵の施錠や解錠は、押しボタンスイッチの接点に、フォトカプラを接続して電子的にスイッチを短絡する動作としました。(押ボタンスイッチの極性を確認して、フォトカプラの接続端子を決めています)
RaspberryPiはネットワーク接続でき、安価なRaspberry Pi ZERO 2Wを使っています。アマゾンさんで4,150円です。microSDカード64GBと専用の電源も購入し、32bitOSをインストールしました。
有線LAN用のUSBアダプタを接続し、Wifi設定後は、Wifiで接続しています。(初期イメージ作成時に、Wifi設定もできるので、有線LANアダプタは、必須ではありませんが)

拡張ボードは、40pinのコネクタでボードの上部に接続するのが一般ですが、CPUチップにヒートシンクをつけていないことや、拡張ボードの方が大きい基盤だったので、下部で接続する方法にしています。
以下は、下部に実装した写真です。RaspberryPi本体の下にも抵抗など実装していますが、LEDやDIPSW、I2Cセンサなどは、RaspberryPi本体の外側に実装しました。電源用のコネクタと押ボタン用とLEDの電圧用の3組の配線が接続されています。DIPSWで動作モードを指定し、押ボタンSWは、マニュアルで施錠操作ができるようにしたものです。LEDは、3つ実装し、周期処理の動作状態や周期アプリの終了状態、マニュアルシャットダウン状態などが確認できるようにしています。

以下は、作成した回路図です。


INA219センサは、モジュールを購入して基盤に設置しました。
アマゾンで1個458円です。

NA219センサは、電圧を測定できるモジュールで、測定した電圧によって、LEDが点灯しているか消灯しているかを確認し、施錠状態を判断します。
LEDが点灯中=電圧が2V以上なら、ボタン押下で解錠
LEDが消灯中=出夏が2V以下なら、ボタン押下で施錠としてスマホ画面を構成します。
作成した画面イメージは、以下のとおりです。

画面を開いたとき(指定URLにアクセスしたとき)に、鍵の状態を表示します。施錠中のときは、解錠ボタンを表示
一方、鍵の状態が、解錠中のときは、施錠ボタンを表示します。

どちらの画面でも、解錠、施錠操作時には、パスコードを必要とする画面にしています。パスコード無しでボタンを押下すると、以下のようにメッセージを表示します。


パスコードが正しく指定できている場合は、サーバ側(RaspberryPI)でGPIO13を制御してスイッチを押下する動作とします。スイッチ操作後に、
1秒まって、鍵の状態(LEDの点灯状態)を確認し、状態の変化を読み出し、画面に状態として表示させます。
上記の動作を実現するために、画面は、Webixライブラリを使って、作成します。ボタン押下時や、状態確認時の動作(RaspberryPI側の動作)は、PHP言語で記述し、パスコードのチェックして正しい場合は、pythonで記述されたI2Cインタフェースを制御するスクリプトを実行します。
画面用のソースsample10.phpは、以下のとおりです。

<?php
    $VER_INFO ="V01L01";
    $TITLE_INFO = "sample10.php";
    $myfilename = basename(__FILE__);   //自分自身のファイル名取得
    define('ROOT_PATH','/var/www/html/webix01'); //ソースを保存しているパス(動作環境に応じて記述する必要あり)
    define('SUB_FOLDER','/webix01');    //サブフォルダを指定したURL
    $userid = 'admin';
    if(isset($_GET['userid'])){
        $userid = $_GET['userid'];
    }
    $logheader = 'userid='.$userid.', '.$myfilename.':';//ログ出力時のヘッダー情報(自ファイル名,ログインIDを付与)
    error_log($logheader.' start '.$myfilename);
?>
<!DOCTYPE html>
<html lang="ja">
    <head>
        <meta charset="UTF-8">
        <link rel="stylesheet" href="/webix_GPL_1020/webix.css" type="text/css"   charset="utf-8">
        <script src="/webix_GPL_1020/webix.js"></script>
        <link href="/webix_GPL_1020/skins/compact.css?<?php echo date('Ymd-H'); ?>" rel="stylesheet" type="text/css">    
        <link rel="icon" href="<?php  echo SUB_FOLDER; ?>/image/webix_64.ico">
        <script src="<?php  echo SUB_FOLDER; ?>/commonlib/moment-with-locales.js"></script>
        <title><?php echo $TITLE_INFO.' ('.$VER_INFO.')' ?></title>
    <style>
    </style>
    </head>
<body>
<script type="text/javascript" charset="utf-8">
webix.i18n.setLocale("ja-JP");
var userid = '<?php echo $userid; ?>';


webix.ui.fullScreen();


function get_key_lock_status(mess_mode){
    var send_prm = {};
    send_prm.userid = userid;
    var xhr =webix.ajax().sync().get("<?php  echo SUB_FOLDER; ?>/rest_api/ZTEST/ZTEST_get_key_lock_status.php",send_prm);
    var resp = xhr.responseText;
    var resp_info = JSON.parse(resp);
    var resp = resp_info.resp;
    if(resp == "ok"){
    	$$("key_status").setValue(resp_info.key_status_jp);
    	if(resp_info.key_status == "lock"){
    		$$("action_btn").setValue("解錠");
    	}
    	else if(resp_info.key_status == "unlock"){
    		$$("action_btn").setValue("施錠");
    	}
    	else{
    		$$("action_btn").hide();
    	}
    	if(mess_mode){
    		webix.message({type:"success",text:"読み出しました。"});
    	}
	}
	else{
    	$$("key_status").setValue("不明");
    	if(mess_mode){
    		webix.alert("鍵の状態確認で<br>エラーしました。");
    	}
	}
}

//画面の初期化
webix.ui({ padding: 10,
    rows:[
        {view:"label", template:"<span style='font-weight:bold; font-size:180%;'>鍵の状態と設定</span>"},
        { margin:5, 
            cols:[
              {view:"text", label:"状態",  value:"",labelWidth:100, width:200,labelAlign:"right",readonly:true,id:"key_status",name:"key_staus"},
                {view:"button",label: "再確認", id:"read_btn",name:"read_btn", width: 100
                    ,click:function(){
                    	get_key_lock_status(true);
                    }
                }
            ]
        },
        {view:"label"},
        { margin:5, 
            cols:[
               {view:"text", label:"パスコード",  value:"",labelWidth:100, width:200,labelAlign:"right",id:"pass_code",name:"pass_code"},
               {view:"button",value: "セット", id:"action_btn",name:"action_btn", width: 100, css:"webix_danger"
                    ,click:function(){
                    	var pass_code = $$("pass_code").getValue();
                    	if(pass_code == ""){
                    		webix.alert("鍵操作時は、<br>パスコードを<br>指定してください。");
                    		return;
                    	}
                    	var key_mode = "lock";
                    	var key_status= $$("key_status").getValue();
                    	if(key_status == "施錠中"){
                    		key_mode = "unlock";
                    	}
                    	else if(key_status == "解錠中"){
                    		key_mode = "lock";
                    	}
                    	
                        var formData = new FormData();
                        formData.append("userid", userid);
                        formData.append("pass_code",pass_code);
                        formData.append("key_mode",key_mode);
                        
                        var xhr =webix.ajax().sync().post("<?php  echo SUB_FOLDER; ?>/rest_api/ZTEST/ZTEST_key_action.php",formData);
                        var resp =  JSON.parse(xhr.responseText);
                        if(resp.resp =="ok"){
                        	$$("key_status").setValue(resp.key_status_jp);
                        	if(resp.key_status == "lock"){
    							$$("action_btn").setValue("解錠");
                        	}
                        	else if(resp.key_status == "unlock"){
    							$$("action_btn").setValue("施錠");
                        	}
                        	else{
     							$$("action_btn").hide();
                       		}
                        	webix.message({type:"success",text:"操作しました。"});
                        	
                            return;
                        }
                        else if(resp.error_code == -2){
                        	webix.alert("パスコードが<br>正しくありません。<br>code="+resp.error_code);
                            return;
                        }
                        else{
                        	$$("key_status").setValue("不明");
                        	webix.alert("鍵の状態変更で<br>エラーしました。<br>code="+resp.error_code);
                            return;
                        }
                    }
                },
            ]
        },
        {view:"label"},
        { margin:5, 
            cols:[
              {width:200},
              {view:"button",label: "閉じる", id:"close_btn",name:"close_btn", width: 100, css:"webix_primary"
                    ,click:function(){
                    	location.href = 'https://www.google.com/?hl=ja';	//google画面にジャンプ
                    }
                }
            ]
        },
        ]
});

var curr_key_status = "unlock";
if ( curr_key_status == "lock"){
	$$("key_status").setValue("施錠中");
    $$("action_btn").setValue("解錠");
}
else if( curr_key_status == "unlock"){
	$$("key_status").setValue("解錠中");
    $$("action_btn").setValue("施錠");
}
else{
  $$("action_btn").hide();
}
$$("pass_code").setValue("");
get_key_lock_status();

</script>
</body>
</html>

スマホからは、以下のURLでアクセスすれば、初期画面が表示されます。

http://192.168.xx.yy/webix01/view/ZTEST/sample10.php?userid=admin
192.168.xx.yyは、RaspberryPiのIPアドレスです。
鍵の状態を読み出すときは、getメソッドで

var xhr =webix.ajax().sync().get("<?php  echo SUB_FOLDER; ?>/rest_api/ZTEST/ZTEST_get_key_lock_status.php",send_prm);

鍵を制御するときは、postメソッドで、サーバ側のPHPをコールします。

var xhr =webix.ajax().sync().post("<?php  echo SUB_FOLDER; ?>/rest_api/ZTEST/ZTEST_key_action.php",formData);

ZTEST_get_key_lock_status.php(鍵の状態を読み出すREST_API)は、以下のとおりです。

 <?php
    //ZTEST_get_key_lock_status.php
    // UIからの鍵の状態を読み出す
    $FUNC_INFO = "ZTEST";
    $VER_INFO ="V01L01";
    $myfilename = basename(__FILE__);   //自分自身のファイル名取得
    $userid = 'admin';
    $logheader = 'userid='.$userid.', '.$myfilename.':';//ログ出力時のヘッダー情報(自ファイル名,ログインIDを付与)

    if($_SERVER["REQUEST_METHOD"] != "GET"){
      //GET以外ははじく
      header("HTTP/1.0 404 Not Found");
      return;
    }
    if(isset($_GET['userid'])){
        $userid = $_GET['userid'];
    }
    $logheader = 'userid='.$userid.', '.$myfilename.':';//ログ出力時のヘッダー情報(自ファイル名,ログインIDを付与)

    //
    //メインルーチン
    //
    error_log($logheader.' rest api request to '.$myfilename);
    $base_folder_info_name = "/home/pi/work/";
    $python3_cmd = "sudo python3 ".$base_folder_info_name."read_key_status.py";
    error_log($logheader."python3_cmd  =".$python3_cmd);
	//cmd 実行
	error_log($logheader."cmd exec:".$python3_cmd) ;
	$key_status = exec($python3_cmd);	//python3コマンドを実行して、結果を変数に代入(lock or unlockが応答される)
	if($key_status == "lock" ){
		$key_status_jp = "施錠中";	//画面表示用の日本語メッセージをセット
	}
	else if($key_status == "unlock"){
		$key_status_jp = "解錠中";	//画面表示用の日本語メッセージをセット
	}
	else{
	    //応答が、lock,unlock以外は、エラー応答
		error_log($logheader.' python3_cmd error code = -1 $key_status='.$key_status);
    	$resp= "ng";
    	$error_code = -1;
		$json_data = json_encode(compact("resp","error_code"),JSON_UNESCAPED_UNICODE);
		exit;
	}
    $resp = "ok";
    $error_code = 0;
    //compact関数とjson_encode関数で、JSON形式の文字列に変換
    $json_data = json_encode(compact("resp","error_code","key_status","key_status_jp"),JSON_UNESCAPED_UNICODE);
    echo $json_data;    //結果をecho関数で出力
?>

鍵の状態は、I2CインタフェースでINA219の電圧を読み出して判断しますが、そのソースread_key_status.pyはpythonで記述しています。INA219のライブラリを使用しています。

#!/usr/bin/env python
from ina219 import INA219
from ina219 import DeviceRangeError

SHUNT_OHMS = 0.1
MAX_EXPECTED_AMPS = 0.2

def read():
    ina = INA219(SHUNT_OHMS, MAX_EXPECTED_AMPS, address=0x40)
    ina.configure()

    #print("Bus Voltage: %.3f V" % ina.voltage())
    # 書き込むファイルのパスを宣言する
    file_name = "/home/pi/work/key_status.txt"
    key_status = "unlock"

    if ina.voltage() > 2:
        key_status = "lock"

    print(key_status)


    try:
        file = open(file_name, 'w')
        file.write(key_status)
    except Exception as e:
        print(e)
    finally:
        file.close()


if __name__ == "__main__":
    read()

PHPからは、

$python3_cmd = "sudo python3 ".$base_folder_info_name."read_key_status.py";
$key_status = exec($python3_cmd);

で実行し、結果は、変数$key_statusに代入します。
今まで記述した内容は、スマホから自宅内のIPアドレスにアクセスして操作する環境のため、自宅外(自宅のWifiが届かないエリア)では、動作しません。外出先から操作する場合は、別途、外部からのアクセスが可能となる環境を構築する必要があります。このあたりは、次回以降で紹介します。
尚、回路図から実際の基板づくりは、ユニバーサル基板に必要部品を実装し、はんだ付けして作成しています。


この記事が気に入ったらサポートをしてみませんか?