建売の戸建てを購入しました。これまでずっとマンション暮らしだったので、戸建てに住むにあたり気になるのは、やはりセキュリティです。家に誰もいないときに、泥棒が入ってきたらどうしよう。セコムやアルソックと契約するしかないのだろうか。でも、毎月何千円も払えない……。我々になすすべはないのか? いや、ある!
それはエリア分けによるセキュリティです。家全体を守るのは難しい。それならば、重要なものを一箇所に集めて、そこだけを守ればよいのです。私さくらは、ウォークインクローゼットに目をつけたのでした。
スポンサーリンク
オープンセサミ、ひらけゴマ!
セサミminiという製品があります。サムターンに貼り付けることで、電波を通じて受け取った指令に基づきモーターが駆動し、サムターンを回してくれる製品です。つまり、ドアの鍵の施錠、解錠をプログラマブルに実行できる装置なのです。
製品コンセプトとしては、玄関のドアに取り付けることで、鍵を持ち歩かなくて良くなるからスゲエいいよ! というものなのですが、個人的には「いや、さすがに玄関にこんなのつけるのはヤバイだろ……。脆弱性があって、自由に玄関のドア開けられるかもしれないじゃん……」と思います。ところがどっこい、これを家の中のウォークインクローゼットの鍵に取り付ける場合はどうでしょう? 玄関のドアは従来どおりのセキュリティで、さらにウォークインクローゼットに電子的なセキュリティが追加されるわけです。これはイケる!
ウォークインクローゼット要塞化計画概要
次の要件を満たすことで、仮に窓破りなどの方法で泥棒が入ったとしても、大切なものを盗まれずに済むと考えます。
- 大切なものはウォークインにまとめる
- ウォークインは施錠ができる
- 家族の誰かが在宅時は常に解錠された状態にする
- 家族全員が外出したら自動で施錠された状態にする
ウォークインクローゼットに鍵をつける
まず、これが問題です。注文住宅ならまだしも、うちは建売なので、すでに素敵な折戸がついています。これにサムターン錠は……つかないだろう。ということで、サムターン錠付きの開き戸に交換してもらうことにしました。
既設のドアのメーカ関係の業者がいいのかなと思って、「リクシル」でググったら出た「リクシルPATTOリフォーム」というところに相談してみました。WEBフォームに相談内容を登録すると、加盟店? の全国にある工務店のうちの一つをアサインしてくれるサービスのようです。相談してみたら、近くの業者さんが下見に来てくれて、「交換できます」とのことでした。なお、既設のドアはLIXIL製でなく、DAIKEN製でした。
交換の見積もりをもらったところ、10万円弱でした。結構高いやん……。と思いましたが、明細を見る感じ、適正価格のように思いましたので、これで対応してもらう予定です。
ちゃんとした鍵付きのドアにする必要あり
LIXILの室内標準ドア、ラシッサSのLAAという製品で見積もってもらいました。オプションで「標準のシリンダー錠」か、「美和製シリンダー錠」をつけることができます。実はこれが非常に重要で、この製品の場合、美和製のシリンダー錠をつけなくてはいけません。
なぜかというと、標準のシリンダー錠には「ドア錠はサムターンをロックした状態で閉めた場合、自動的に解除される機構付です」という注釈があり、これがヤバイんです。どういう機能なのかというと、普通はドアを閉めてから施錠しますが、ドアを開けたまま施錠して、さらにそのままドアを閉めたときに、自動で鍵が開きます、という機能なのです。「自動」というのがキモで、これをどう実現しているかというと、ドアから斜めに飛び出ているデッドボルト(ドア枠に引っかかってカンヌキになる部分)が、ドアを閉めることによりドア枠に押されて、斜めにズズズーっとスライドしてドアに押し込まれると、解錠されるようになっているのです。
これって要するに、デッドボルトをドアに押し込めば鍵が開くってことなんです。ドアとドア枠の間には隙間がありますよね。ドアが閉まって施錠されている状態で、この隙間に、デッドボルトをまたぐようにして細い紐を通します。あとは細い紐を手前に引くと、斜めになっているデッドボルトは紐に押されて、ドアに押し込まれます。すると、施錠されていたはずなのに、解錠されてしまうのです。
「標準のシリンダー錠」はトイレのドアなど、セキュリティというよりは、在籍確認程度の用途で使うものなのでこれでいいのですが、セキュリティ用途で使うには問題があります。その点、「美和製シリンダー錠」ではそのような動作をしないので、今回は「美和製シリンダー錠」オプションにしました。具体的には美和のDAシリーズのDA-1という錠で、部屋側をシリンダー錠、ウォークイン側をサムターン錠にします。
GPSをトリガにしてセサミを自動操作
ウォークインクローゼットに鍵がつくのはいいとして、その鍵を自分で開けたり閉めたりする必要があると、かなり使い勝手が悪いです。セキュリティと利便性はトレードオフの関係にありますが、今回はこれを、iPhoneのGPSを利用して、自動化することにしました。
大変すばらしいことに、セサミはWeb APIで操作することができます。ただし、セサミ本体にはBluetoothしか乗っていないので、これをインターネットに接続するためのオプション品であるセサミWi-Fiアクセスポイントを購入します。このAPは、USBポートに接続するだけでOKです。あとは、セサミのiPhoneのアプリでちょちょっと操作すると、セサミ本体とAPがBluetoothで接続されて、さらにAPと自宅の無線LAN APがWi-Fiで接続され、インターネット越しにセサミ本体を操作できるようになります。
なお、Web APIなど使わずとも、セサミ本体とWi-Fiアクセスポイントさえ用意してしまえば、「自宅に近づいたら自動で解錠されて、30秒程度経過するとまた自動で施錠する」程度のことはiPhoneのセサミアプリでできる(らしい)です。いわゆる、玄関のドアに取り付けて使用するような場合であれば、以上で終了です。
ただ、今回は「在宅している間はつねに解錠しておく」「全員が外出したら施錠する」という要件なので、通常のアプリでは実現不可能です。これを実現するには、ステート管理が必要です。そこで、GPS情報についてはIFTTTを使い、セサミコマンドの発行と状態管理にはGoogle Apps Script(とGoogle Spreadsheet)を使うことにしました。
自宅にあるraspberry piを利用して、自宅Wi-FiにiPhoneが接続されているかどうかを定期的に監視する(arp-scan -l)という案も考えましたが、やってみたら、iPhoneがスリープするたびにarp応答しなくなるので無理でした。
If This Then That
私もよくは知りませんが、iPhoneにこのアプリを入れることで、Location情報をトリガにして、何かができます。「もしthisなら」の、thisを「location」サービスにします。locationサービスの「ある圏内に入ったら」と「ある圏内から出たら」というトリガを使用することで、「自宅圏内に入ったとき」と「自宅圏内から出たとき」のそれぞれについて、何かをすることができます。
なにか、というのが、「そんならthat」の、thatにあたります。thatは「Webhooks」サービスにします。これは指定のURLにHTTPリクエストを投げる事ができるサービスです。ここに例えば、セサミのWeb APIのURLを指定すれば、セサミの公式アプリなしで、Web APIからセサミを操作することができます。
ただ、IFTTTではステート管理ができません。thisならthatするだけなので、例えば「私が外出したら、鍵を閉める」というのはできますが、「私が外出したら、家に他に誰もいないか確認して、誰もいなければ鍵を閉める」というのはできません。
そこで、IFTTTが検知したGPS情報をもとに、いったん別のWebサービスにWebhooksで情報を飛ばして、そのWebサービスでステート管理とセサミの操作を行う必要があります。
Google Apps Script
これも知らなかったんですが、Googleが無償で提供している、Javascript動作環境のようです。ひとつは、同じくGoogleが提供しているSpreadsheetなどのアプリでマクロを実現するために利用できるようです。VBAみたいな感じですね。
Google Apps Script、いわゆるGASのすごいところは、これだけでかんたんにWebアプリ(Web Apps)を作れちゃうことです。Webアプリってなに? かんたんってなに? と思いましたが、マジでかんたんにできます。詳しいことはよくわかりませんが、Webアプリなんて作ったことのない私が、朝調べ始めて、日付が変わる頃にはもう完成していました。これはなんかすごい!
Spreadsheet
まず、ステート管理のためのシートを、Google Spreadsheetで作ります。
Var Value
TARO TRUE
HANAKO FALSE
SesameLocked FALSE
TAROが在宅のときは、値がTrueになります。HANAKOも同様です。セサミが施錠されているとSesameLockedがTrueになります。
作成したシートのURLの一部がIDになっており、GASからこのIDを指定することで、シートの内容を読み書きできます。
doPost(e)
続いて、Webアプリのパラメータ受け口を作成します。このWebアプリのURLにPOSTでリクエストが来ると、doPost(e)の内容が実行されます。引数eのpostDataをgetDataAsStringしてJSONにパースすると、POSTされたJSONにかんたんにアクセスできるのです。なお、当方素人のため、何を言っているのか自分でもよくわかっていません。
このWebアプリの前提として、IFTTTのWebhooksがLocation情報をもとにキックされて、Content-Type: application/jsonで、リクエストボディ「{ “person”: “TARO”, “exists”: “true” }」をこのWebアプリのURLに対してPOSTします。それぞれのパラメータについて、太郎は自分のiPhoneに入れたIFTTTで、このボディ部をperson: TAROにしますし、花子は同様にperson: HANAKOにします。また、IFTTTのレシピは「出たとき」と「入ったとき」の2通り作成し、それぞれボディ部をexists: Falseとexists: Trueにします。
これにより、太郎が自宅から出たら、IFTTTがそれを検知して、Webhooksでperson: TARO, exists: falseという情報をこのWebアプリに送信するというわけです。
Webアプリは、受信した情報をspreadsheetに書き込みます。ここでTARO_EXISTSは定数で、’B2’です(さっきのシートのB2セル)。
完成したら、スクリプトエディタから「ウェブアプリケーションとして導入」という操作を行うのですが、ここでProject versionを指定する必要があります。ここは必ずNewにしたほうが良いです。Newにしない場合、更新はされるのですが、POSTがうまく飛ばせなくなります。よくわからんのですが、Newでないときは、HTTPのリダイレクト処理を使って更新されるそうです。curlコマンドでPOSTする場合には-Lオプションを付けることでちゃんと飛ぶのですが、Webhooksから飛ばすPOSTではリダイレクトに追従できないようで、エラーになります。
function doPost(e) {
var output = "";
try {
const spreadsheet = SpreadsheetApp.openById(SS_ID);
const sheet = spreadsheet.getSheetByName(SH_NAME);
const params = JSON.parse(e.postData.getDataAsString());
if(params.person === "TARO"){
sheet.getRange(TARO_EXISTS).setValue(params.exists);
} else if(params.person === "HANAKO"){
sheet.getRange(HANAKO_EXISTS).setValue(params.exists);
}
output = "successed";
console.log("successed");
} catch(ex) {
output = "failed";
console.log("failed");
}
return HtmlService.createHtmlOutput(output);
}
ここでは、最後にHtmlServiceのHtmlOutputオブジェクトを返すようにしています。当初ContentServiceのTextOutputオブジェクトを返したところ、IFTTTから「Action failed. Unable to make web request. Your server returned a undefined」といわれて、Applet failed扱いになってしまいました。linuxからcurlで投げたらちゃんと返ってきたんですが、よくわかりませんでした。試しにHtmlOutputで返したらIFTTTが正常終了するようになったので、そうしました。
セサミのステータス管理
ここでもう一つ、GASのすごいところなんですが、実はGASは定期実行できます。作ったスクリプトを、分単位で定期的に実行することができるのです。
そういうわけで、シートから家人の在宅状況を監視して、家に誰もいなければ鍵を閉めて、一人でもいたら鍵を開けるスクリプトを作成し、これを1分ごとに実行するようにしました。TAROとHANAKOの二人家族を想定していますが、人が増えたときはOR条件を追加すれば良いでしょう。lockSesame関数は、trueを引数にして呼び出すと、施錠動作を行い、成功したらtrueを返す関数です。ちゃんと施錠できたときは、シートのSesameLockedセルをtrueにします。
function updateSesameStatus() {
const spreadsheet = SpreadsheetApp.openById(SS_ID);
const sheet = spreadsheet.getSheetByName(SH_NAME);
let result = true;
if(!(sheet.getRange(TARO_EXISTS).getValue() || sheet.getRange(HANAKO_EXISTS).getValue())){ // TARO and HANAKO neither exist.
if(!sheet.getRange(SESAME_LOCKED).getValue()){ // not locked
result = lockSesame(true);
if(result){
sheet.getRange(SESAME_LOCKED).setValue(true);
}
}
}else{ // TARO or/and HANAKO exist.
if(sheet.getRange(SESAME_LOCKED).getValue()){ // locked
result = lockSesame(false);
if(result){
sheet.getRange(SESAME_LOCKED).setValue(false);
}
}
}
return result;
}
セサミを操作する関数
先ほどの関数から呼び出される、セサミを操作するための関数です。セサミのWeb APIに対してJSONでリクエストを投げているだけです。セサミのWeb APIについては、公式のドキュメント(CANDY HOUSE’s Sesame API)であっさり分かります。
また、実行結果を気軽に知りたいなと思ったので、LINEで通知が来るようにしました。LINE Notify APIというものがあり、これを使うと、鍵を閉めたとき、閉じたときにLINE Notifyというアカウントが話しかけてきます。
function lockSesame(lock = true) {
let command = lock ? "lock" : "unlock";
const body = {
"command": command
};
const exeOptions = {
"method": "post",
"contentType": "application/json",
"payload": JSON.stringify(body),
"headers": {"Authorization": SESAME_API},
"muteHttpExceptions": true
};
let response = UrlFetchApp.fetch(SESAME_URL + '/sesame/' + SESAME_DEVID, exeOptions);
console.log(response.getContentText());
let params = JSON.parse(response.getContentText());
const queryOptions = {
"method": "get",
"contentType": "application/json",
"headers": {"Authorization": SESAME_API},
"muteHttpExceptions": true
};
do{
Utilities.sleep(3000);
response = UrlFetchApp.fetch(SESAME_URL + '/action-result?task_id=' + params.task_id, queryOptions);
console.log(response.getContentText());
params = JSON.parse(response.getContentText());
}while(params.status !== "terminated");
console.log("successful: " + params.successful);
console.log("error: " + params.error);
let lineMessage = "message="
lineMessage = lineMessage + (lock ? "鍵を閉めるのに" : "鍵を開けるのに");
lineMessage = lineMessage + (params.successful ? "成功したよ" : "失敗したよ");
const notifyOptions = {
"method": "post",
"payload": lineMessage ,
"headers": {"Authorization": "Bearer " + LINE_API },
"muteHttpExceptions": true
};
response = UrlFetchApp.fetch(LINE_URL, notifyOptions);
console.log(response.getContentText());
return params.successful;
}
ちゃんと動いた
外出したり戻ってきたりして、想定通りセサミが動くことを確認できました。ただ、トリガーがGPSなので、ちゃんと発動しない場合もあると思います。そういうときに便利なのがiPhoneのShortcutアプリです。作成したGASのWebアプリURLに対して、家人が全員いるとか、いないとかの状態にするためのPOSTをするレシピを作成すれば、スプレッドシートの内容を好きな状態にリセットできます。
肝心のウォークインクローゼットの工事が終わっていないのですが、それが終わればあとはセサミを取り付けるだけです。
このように、便利な装置と、素晴らしい無料サービスを組み合わせることで、比較的安価にスマートでセキュリティの高いホームが実現できそうです。全然需要なさそうですが、同じようなことをしたい方の参考になれば!
トリガー不発をショートカットで拾う
iOS 14.2から、ショートカットアプリのオートメーション機能で、所定の時刻になったらショートカットを自動実行できるようになりました。ただし、自動実行するショートカットにLocationが含まれている場合、自動実行されません。代わりに、接続しているWi-Fiのネットワーク名を基準に在宅かどうかをPOSTするショートカットを作成し、これを実行するオートメーションを24個、1Hずつ実行時刻をずらして作成することで、1H間隔で在宅か不在かをチェックできます。
接続しているWi-Fiのネットワーク名を基準に在宅かどうかをPOSTするショートカット
[SCRIPTING] Get Wi-Fi network's Network Name
[SCRIPTING] If Network Details begins with 自宅Wi-FiのESSID
[SHORTCUTS] RUN スプレッドシートに自分が在宅であるフラグを立てるショートカット
[SCRIPTING] Otherwise
[SHORTCUTS] RUN スプレッドシートに自分が不在であるフラグを立てるショートカット
[SCRIPTING] End If
1Hずつ実行時刻をずらして作成したオートメーション
When: At 8:00, daily
Do: Run Shortcut 接続しているWi-Fiのネットワーク名を基準に在宅かどうかをPOSTするショートカット
Ask Before Running: false
When: At 9:00, daily
Do: Run Shortcut 接続しているWi-Fiのネットワーク名を基準に在宅かどうかをPOSTするショートカット
Ask Before Running: false
When: At 10:00, daily
Do: Run Shortcut 接続しているWi-Fiのネットワーク名を基準に在宅かどうかをPOSTするショートカット
Ask Before Running: false
...
...
参考
- 3分で API を作って世の中にデプロイするライブコーディング〜今日から君もスピードスターエンジニア〜
- GASのdoPost関数をcurlでテストする時リダイレクトが必要なら-Xオプションを使わない
- GOOGLE APPS SCRIPT(GAS)でLINEに通知