スマートロック SESAME5 をスマホでもESP32でも操作できるように

スマートロックSESAME5

安価でいろいろな鍵に対応したスマートロック SESAME5 を導入しました。

https://jp.candyhouse.co/products/sesame5?variant=40074599792703【New】SESAME5

SESAMEの素晴らしいところは、スマホアプリでの操作はもちろん、Web APIの対応、さらにはBluetoothSDKまで公開されているところです(約一月前に公開されました)。

Web API の利用は手軽ですが、デメリットはWiFiモジュールが別途必要になる点、月に3000回を超えるAPI呼び出しは有料となる点です。
無料で使うとすると90秒に1度の状態取得が限界となるので、リアルタイムに反応するようなアプリケーションを作るのは厳しいでしょう。

そんな時はBluetoothSDKを使用することになります。SDKiOS/Mac向けAndroid 向け、 そしてなんと ESP32 向け が用意されています。今回はこの ESP32 向けを使ってみました。


Bluetooth APIドキュメントも用意されていますので、SDKだけ見ても分からない時はこれを読むと良いでしょう(中国語なので適宜翻訳にかけつつ)。

ESP-IDF の導入

ESP32 向けSDKESP-IDF を使って開発されています。Arduino-ESP32ではBluetoothSDK (NimBLE) の違いから利用しません。

以下 macOS 向けに ESP-IDF を導入する方法です。まず brew を使ってコンパイラとcmakeを導入。

brew tap tasanakorn/homebrew-esp32
brew install xtensa-esp32-elf cmake

ESP-IDF を git clone します。

git clone --depth=1 --recursive https://github.com/espressif/esp-idf.git
cd esp-idf
./install.sh

あとはESP-IDFを利用したいターミナルを起動するたびに、以下を実行します。(/path/to はclone先)

source /path/to/esp-idf/export.sh

Arduino で ESP32 を利用していない場合、使用するESP32ボードによっては別途USBシリアルドライバの導入が必要になる場合があります。
CP210x USB to UART Bridge VCP Drivers - Silicon Labs

ESP32向けSESAME SDKを動作させてみる

SDKをダウンロードし、書き込むには以下のようにします。今回はESP32として M5Stick-C を利用しました。

M5StickC
git clone https://github.com/CANDY-HOUSE/SesameSDK_ESP32_with_DemoApphttps://github.com/CANDY-HOUSE/SesameSDK_ESP32_with_DemoApp.git
cd SesameSDK_ESP32_with_DemoApp
idf.py set-target esp32  # ターゲットボードに合わせる
idf.py -p /dev/cu.usbserial-XXXXXXXXXX -b 1500000 flash  # ESP32を接続したときに認識されたシリアルデバイスを指定 / 1.5Mbpsで転送
idf.py monitor

もしSESAME5が未登録の状態であれば、デバイスに接続と登録が行われ、鍵を開けると自動的に閉まるようになるはずです。
しかし、通常はすでにスマートフォンに登録済みでしょう。これを登録したまま利用するには、少し追加手順が必要です。

バイスシークレットの取得

まず、スマートフォンアプリでロックを選択して「…」メニーから「このセサミの鍵のシェア」を選び、オーナー鍵を表示させます。
QRコードが表示されるので、これを保存して適当なアプリ(QRコードリーダー など)で認識させると、 "ssm://...&sk=xxxxxx&..." という文字列が取得できます。
このskの内容をbase64でコードすると、 0x05 [デバイスシークレット16B] ... となっているので、このデバイスシークレット部分を取り出します。
具体的には sk= 以降の部分をコピーし、

pbpaste | base64 -d | LANG=C cut -c 2-17 | xxd

を実行すれば16進数で表示できます。

デモアプリの書き換え

デモアプリは登録済みのSESAMEには接続しないようになっているので、接続できるように変更を加えます。

まず main/blecent.c の ssm_scan_connect() で、接続スキップ部分を変更。ついでに電波強度がかなり強くないと接続しないようになっているので、これも外しておきます。

--- a/main/blecent.c
+++ b/main/blecent.c
@@ -113,20 +116,28 @@ static int ble_gap_connect_event(struct ble_gap_event * event, void * arg) {
 
 static void ssm_scan_connect(const struct ble_hs_adv_fields * fields, void * disc) {
     ble_addr_t * addr = &((struct ble_gap_disc_desc *) disc)->addr;
-    if (((struct ble_gap_disc_desc *) disc)->rssi < -60) { // RSSI threshold
+    int rssi = ((struct ble_gap_disc_desc *) disc)->rssi;
+    if (!fields->mfg_data) {
         return;
     }
     if (fields->mfg_data[0] == 0x5A && fields->mfg_data[1] == 0x05) { // is SSM
         if (fields->mfg_data[4] == 0x00) {                            // unregistered SSM
-            ESP_LOGW(TAG, "find unregistered SSM[%d]", fields->mfg_data[2]);
+            ESP_LOGW(TAG, "find unregistered SSM[%d] rssi=%d", fields->mfg_data[2], rssi);
             if (p_ssms_env->ssm.device_status == SSM_NOUSE) {
                 memcpy(p_ssms_env->ssm.addr, addr->val, 6);
                 p_ssms_env->ssm.device_status = SSM_DISCONNECTED;
                 p_ssms_env->ssm.conn_id = 0xFF;
             }
         } else { // registered SSM
-            ESP_LOGI(TAG, "find registered SSM[%d]", fields->mfg_data[2]);
-            return;
+            ESP_LOGI(TAG, "find registered SSM[%d] addr=%s rssi=%d", fields->mfg_data[2], addr_str(addr->val), rssi);
+            if (p_ssms_env->ssm.device_status == SSM_NOUSE) {
+                if (memcmp(p_ssms_env->ssm.addr, addr->val, 6) == 0) {
+                    p_ssms_env->ssm.device_status = SSM_DISCONNECTED;
+                    p_ssms_env->ssm.conn_id = 0xFF;
+                } else {
+                    return;
+                }
+            }
         }
     } else {
         return; // not SSM

この状態でESP32に書き込んで起動すると、登録済みのSESAME5のアドレスが表示されるはずです。

find registered SSM[5] addr=11:22:33:44:55:66 rssi=-75

このアドレスを逆順にしたもの*1と、上記で取得したデバイスシークレットをmain.cの初期化時にp_ssms_envに書き込んでおきます。

diff --git a/main/main.c b/main/main.c
index cf78515..888e08e 100644
--- a/main/main.c
+++ b/main/main.c
@@ -15,5 +15,7 @@ void app_main(void) {
     ESP_LOGI(TAG, "SesameSDK_ESP32 [11/24][087]");
     nvs_flash_init();
     ssm_init(ssm_action_handle);
+    memcpy(p_ssms_env->ssm.addr, (uint8_t[]){0x66, 0x55, 0x44, 0x33, 0x22, 0x11}, 6);
+    memcpy(p_ssms_env->ssm.device_secret, (uint8_t[]){0xde, 0xad, 0xbe, 0xaf, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c}, 16);
     esp_ble_init();
 }

これを再び書き込んで起動させると、今度は接続ができるはず。
SESAME5を動かすとESP32にその状況がリアルタイムに通知され、シリアルにメッセージが表示されます。
また、解錠すると即座に再びロックされるという動きをします。これは main.c に書かれた以下の関数によるものです。

static void ssm_action_handle(sesame * ssm) {
    ESP_LOGI(TAG, "[ssm_action_handle][ssm status: %s]", SSM_STATUS_STR(ssm->device_status));
    if (ssm->device_status == SSM_UNLOCKED) {
        ssm_lock(NULL, 0);
    }
}

あとはこれを好きなように書き換えれば、アプリケーションを作ることができるでしょう。

もちろん ESP32 が接続中も、ちゃんとスマートフォンアプリからも接続して操作が可能です。
また、ESP32からの操作時も履歴がきちんと表示されます。

SESAME ESP32として操作履歴が記録される

*1:NimBLEの仕様みたいです