5
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

身の回りの困りごとを楽しく解決! by Works Human IntelligenceAdvent Calendar 2022

Day 5

スマートロックのセサミをさらに便利にする(アレクサからパスコードレスで解錠&オートロック化)

Last updated at Posted at 2022-12-12

身の回りの困りごと

我が家のスマートロックがちょっとスマートじゃないこと

我が家のスマートロック事情

なぜSESAMEを使うのか

  • 製品が全体的に安い 圧倒的に安い
    • ダブルロックでSESAMEも2つ必要だしマンションの入り口のオートロック解除用にbotも2つ必要なので値段は大事
  • APIが公開されている

しかし。。。

SESAMEはまだ完璧ではない

ギリギリ族の私にとって鍵を開ける時間も閉める時間も惜しいのです
早起きは無理なのです

具体的に何に困っているのかというと

1. Alexa経由でのSESAMEの解錠がスマートじゃない

公式のAlexaスキルでSESAMEを解錠しようとするとパスコードを要求されるため鍵を開けるのに2回指示しないといけない
しかも、我が家はダブルロックでSESAMEが2つあるのでAlexaに4回指示する必要がある

私「アレクサ、『上の鍵』を開けて」
アレクサ「『上の鍵』の確認コードはなんですか?」
私「xxxx」
アレクサ「ロック解除しています。お待ちください」
鍵「ウィーンガチャン」

私「アレクサ、『下の鍵』を開けて」
アレクサ「『下の鍵』の確認コードはなんですか?」
私「yyyy」
アレクサ「ロック解除しています。お待ちください」
鍵「ウィーンガチャン」

なんてめんどくさいんだ!!!

2. SESAMEのオートロックは秒数設定しかできないのでスマートじゃない

設定秒数が短すぎると荷物を受け取ってる間に施錠されてしまったり
設定秒数が長すぎるとドアが閉まってから施錠されるまで待たされたり

かといって本当の意味でのオートロック機能がついてるスマートロックは高い。。。

理想を追い求めてみる

ゴール1. Alexaへ1回の指示だけで2つの鍵を解錠できること

ゴール2. ドアが閉まったら鍵が閉まること

Alexaへ1回の指示だけで2つの鍵を解錠できるようにする

具体的にどうするかと言うと
Alexa → IFTTT → GAS → SESAME API という流れで解錠できるようにする

1. GAS → SESAME API の連携

1-1. SESAMEのAPI利用に必要なパラメータとGASのエンドポイント認証用のハッシュ値を用意する

  • SESAMEのuuidsecretKeyapiKeyを取得する(参考)
  • 任意の文字列をsha512でハッシュ化し、GASのエンドポイントで認証するためのハッシュ値を作る
    • Macならターミナルから shasum で簡単に生成できます(サンプルなのでくれぐれもこのハッシュ値は使わないように。。。。)
    • ここでは行いませんがストレッチングなどもお好みで
      $ echo -n 'oreno_password_daze' | shasum -a 512
      40fc44138fa114b4c7a604052e8e0f7f105c1123ab7b8410870ade930b87857f0c339e52f24f8d76265cacfaddbdce7cd8dd2cbfce5633c742f5e1aa7d4bcb79  -
      

1-2. 秘匿情報をGASのスクリプトプロパティに設定する

先ほど用意したapiKeyなどは秘匿情報なのでソースに書かず以下のような形でスクリプトプロパティに設定する(プロジェクトの設定の中)

API_KEY: YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY
DEVICES: [{ "uuid": "AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA", "secretKey": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"}, { "uuid": "BBBBBBBB-BBBB-BBBB-BBBB-BBBBBBBBBBBB", "secretKey": "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB" }]
PASSWORD_DIGEST: 40fc44138fa114b4c7a604052e8e0f7f105c1123ab7b8410870ade930b87857f0c339e52f24f8d76265cacfaddbdce7cd8dd2cbfce5633c742f5e1aa7d4bcb79

スクリーンショット 2022-12-08 23.46.37.png
スクリーンショット 2022-12-08 23.49.07.png

1-3. スクリプトプロパティから秘匿情報を取得する処理を書く

DEVICES はオブジェクトの配列で扱うためにJSONで保存しているのでパースします

credentials.gs
const passwordDigest = PropertiesService.getScriptProperties().getProperty('PASSWORD_DIGEST');
const apiKey = PropertiesService.getScriptProperties().getProperty('API_KEY');
const devices = JSON.parse(PropertiesService.getScriptProperties().getProperty('DEVICES'));

1-4. GASでAES-CMAC署名できるようにする

cryptojs-aes.min.js.gs
/*
CryptoJS v3.1.2
code.google.com/p/crypto-js
(c) 2009-2013 by Jeff Mott. All rights reserved.
code.google.com/p/crypto-js/wiki/License
*/
var CryptoJS=CryptoJS||function(u,p)....割愛
cmac.min.js.gs
/*
 * The MIT License
 *
 * (MIT)Copyright (c) 2015 artjomb
 */
!function(t){var r;r=t.hasOwnProperty(....割愛

1-5. 認証とSESAMEのAPIを叩く処理を書く

main.gs
function doPost(e) {
  const params = JSON.parse(e.postData.getDataAsString());

  const encodedUserName = Utilities.base64Encode(params.userName, Utilities.Charset.UTF_8);
  const action = { toggle: 88, lock: 82, unlock: 83 }[params.action];

  if (isAuthed(params.password)) {
    UrlFetchApp.fetchAll(
      devices.map(device => ({
        url: `https://app.candyhouse.co/api/sesame2/${device.uuid}/cmd`,
        method : "post",
        payload: JSON.stringify({ cmd: action, history: encodedUserName, sign: generateCmacSign(device.secretKey) }),
        headers: { 'x-api-key': apiKey },
      }))
    );
  }

  return ContentService.createTextOutput(JSON.stringify({ result: 'ok' }));
}

function isAuthed(inputPassword) { 
  raw = Utilities.computeDigest(Utilities.DigestAlgorithm.SHA_512, inputPassword, Utilities.Charset.UTF_8);

  // https://qiita.com/tatataichi/items/7e3a27e1b69200d3fd86#%E3%82%B3%E3%83%BC%E3%83%89%E5%86%85%E5%AE%B9
  let inputPasswordDigest = ''; //ハッシュ値の文字列
  let numHash = 0;  //ハッシュ値の数値

  for (i = 0; i < raw.length; i++) {
    numHash = raw[i];
    if (numHash < 0) { //ゼロ未満だったら
      numHash += 256;  //マイナスの除去
    }
    if (numHash.toString(16).length == 1) { 
      inputPasswordDigest += '0'; //1桁なら0をくっつける
    }
    inputPasswordDigest += numHash.toString(16); //16進数表記の文字列に変換
  }

  return passwordDigest === inputPasswordDigest;
}

// https://zmzlz.blogspot.com/2021/06/sesame3-sesame-os2-gas-web-api.html
function generateCmacSign(secKey) {
  const date = Math.floor(Date.now() / 1000);
  const dateDate = new DataView(new ArrayBuffer(4));
  dateDate.setUint32(0, date, true);
  const msg = dateDate.getUint32(0).toString(16).slice(2, 8);  
  const hex = CryptoJS.enc.Hex.parse;
  return CryptoJS.CMAC(hex(secKey), hex(msg)).toString();
}

1-6. ウェブアプリとしてデプロイする

こんな感じで4つのファイルになっていればok
スクリーンショット 2022-12-06 1.08.56.png

あとは右上の「デプロイ」から「ウェブアプリ」としてデプロイする
スクリーンショット 2022-12-08 21.44.39.png
スクリーンショット 2022-12-08 21.44.46.png
スクリーンショット 2022-12-08 21.44.57.png
スクリーンショット 2022-12-08 21.45.14.png

デプロイ後に発行されるこんな感じのURLをメモっておく
https://script.google.com/macros/s/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/exec

2. IFTTT → GAS の設定

2-1. 右上の Create からAppletの作成

スクリーンショット 2022-12-06 14.14.34.png

2-2. If This を設定

スクリーンショット 2022-12-06 10.57.31.png

2-3. Amazon Alexa を選択

スクリーンショット 2022-12-06 10.57.46.png

2-4. Say a specific phrase を選択

スクリーンショット 2022-12-06 10.57.51.png

2-5. Alexaアカウント連携して鍵を開ける時のフレーズを設定

スクリーンショット 2022-12-06 10.58.19.png

2-6. Then That を設定

68747470733a2f2f71696974612d696d6167652d73746f72652e73332e61702d6e6f727468656173742d312e616d617a6f6e6177732e636f6d2f302f3233363937382f62626562626535662d353233662d333237612d386539632d6131303733643262666165322e706e67.png

2-7. Webhooks を選択

スクリーンショット 2022-12-06 10.59.52.png

2-8. Make a web request を選択

スクリーンショット 2022-12-06 10.59.58.png

2-9. web requestの設定

スクリーンショット 2022-12-09 0.11.47.png

  • URL はGASをデプロイした時に発行されたURL (https://script.google.com/macros/s/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/exec)
  • MethodPOST
  • Content Typeapplication/json
  • Body{ "password": "oreno_password_daze", "userName": "alexa_ifttt", "action": "unlock" }
    • password の値には credentials.gs に定義した passwordDigest の作成時に使った(ハッシュ化前の)文字列
    • userName はSESAMEのアプリの操作履歴に表示されるので自身がわかりやすい文字列を
    • action は今回は解錠が目的なので unlock

こんな感じで Create action

2-10. あとはContinue, Finishで設定完了

スクリーンショット 2022-12-08 21.53.08.png
スクリーンショット 2022-12-08 21.53.15.png

Applet Title は適当に任意の名前で

3. Alexa → IFTTT の設定

3-1. アプリからAlexaの提携アクションを新規作成する

3-2. 定型アクション名を決める

ここの名前もお好みで適当に

3-3. 実行条件を設定する

音声 を選択してフレーズを決める

3-4. アクションを設定する

IFTTT を選択してさきほど作成したアプレットを選択する

こんな感じで設定したら保存する

これでAlexa経由での解錠の問題は解決!
「アレクサ、解放してくれ!」の1回で鍵が2つとも開く!

ドアが閉まったら鍵が閉まるようにする

1. SwitchBot開閉センサSwithBot Hub Miniを購入・設置しAlexaと連携させる

Alexaとの連携はこちらの公式の記事を参考に

2. SESAMEもAlexaと連携させる

こちらも公式の記事を参考に

3. Alexaアプリで定型アクションを設定する

3-1. 定型アクション名を設定

3-2. 実行条件を設定

開閉センサの「閉」をトリガーに設定する

3-3. アクションを設定

SESAMEの施錠をアクションに設定する

我が家は鍵が2つあるので施錠のアクションを2つ設定する

これで扉が閉まるとSESAMEも施錠されるようになりました!
便利便利

これで完璧我が家のスマートロック

靴を履きながらAlexaに解錠を指示したり
家を出たら鍵が閉まるのを横目に確認しながらエレベータを呼んだり
SwitchBot開閉センサは開けっぱなしの時に通知したり、動体検知したら通知したりもできるので便利

SESAMEに限らず

APIが解放されてたりAlexaに対応してたりIFTTT対応しているスマートロックなら同じことできそう

IFTTT、GAS経由で解錠できるようすることのリスクについて

  • GASのソースには秘匿情報置いていないので流出しても問題がない
  • Alexa経由での解錠時にパスコードが不要になったがフレーズをユニークにすることでリスクを抑えることできる
    • また、もともとパスコードを声で言う必要があるのでどっちにしても盗聴に対するリスクは変わらない
  • Alexaをドアや窓の近くに置かないという定番の対策もしておくに越したことはない

万が一IFTTTにあるパスワードやスクリプトプロパティにあるAPI_KEYなどが流出した場合

  • スクリプトプロパティにあるPASSWORD_DIGEST を変更する
  • SESAMEの管理画面 から API KEY のリフレッシュする
  • SESAME自体をリセットして Secret Key をリフレッシュする

※ この仕組みで何かあっても責任は取れないので導入する際には自己責任でお願いします

5
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?