建売の戸建てを購入しました。これまでずっとマンション暮らしだったので、戸建てに住むにあたり気になるのは、やはりセキュリティです。家に誰もいないときに、泥棒が入ってきたらどうしよう。セコムやアルソックと契約するしかないのだろうか。でも、毎月何千円も払えない……。我々になすすべはないのか? いや、ある!
それはエリア分けによるセキュリティです。家全体を守るのは難しい。それならば、重要なものを一箇所に集めて、そこだけを守ればよいのです。私さくらは、ウォークインクローゼットに目をつけたのでした。

スポンサーリンク

オープンセサミ、ひらけゴマ!

セサミ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
...
...

参考

Posted in デジモノ, 情報技術, 生活

コメントを残す

メールアドレスが公開されることはありません。

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください