スマートロック「SESAME mini」とRaspberry Pi Zero WHを使ってWeWorkのオフィスの鍵をICカードで解錠／施錠できるようにした

弊社は[WeWork](https://www.wework.com/ja-JP)のプライベートオフィスに入居しています。ビル内のラウンジエリアなどの共用スペースはWeWorkメンバー用のICカードで解錠できるのですが、オフィスの部屋の鍵は物理キーになっているのがほんのり不満でした。部屋もICカードで開けられたら便利ですのに。

何か方法がないかWeWorkのサポートに問い合わせたところ、市販のスマートロックを利用している会社があるという話だったので、弊社もそれに倣うことにしました。

# セサミminiを導入

工事不要でドアに取り付け、スマホのアプリで鍵の開け締めができる、いわゆるスマートロックはさまざまなメーカーから発売されています。

ただ、WeWorkのプライベートオフィスの鍵はちょっと特殊な形状をしている（取付可能なエリアの幅が狭い、サムターンのつまみが薄い）ため、どんなスマートロックでも取り付けられるわけではありませんでした。いろいろと調べた結果、[セサミmini](https://jp.candyhouse.co/)なら大丈夫そうだと判断し、オンラインショップから購入しました。

決めたポイントは以下の点です。

1. 幅が57mmとコンパクトなのでプライベートオフィスのドアにも取り付けられる
2. どんな形状の鍵でも3Dプリンターでアダプターを製作してくれる
3. 物理キーも引き続き使用できる（夜間の清掃に入ってもらうため必要）
4. 価格が比較的安く、ランニングコストが一切かからない

特に2点めが他とは異なる決め手になりました。実際に、鍵の写真をメールしたら即レスがあり、アダプターを送付してくれるという神対応でした。

そのおかげで、無事にスマホアプリで解錠／施錠ができるようになりました。
![sesame01.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/116818/2a6446d4-bd7c-c610-6f84-82b2a2d9af4b.gif)

色のバリエーションにマットブラックがあったのも良かったですね。ドアの色と合っていていい感じです。

# セサミ WiFi アクセスポイントを追加で導入

セサミ単体ではスマホアプリとのBluetooth接続しかできませんが、専用の[WiFiアクセスポイント](https://jp.candyhouse.co/blogs/how-to/wifi%E3%82%A2%E3%82%AF%E3%82%BB%E3%82%B9%E3%83%9D%E3%82%A4%E3%83%B3%E3%83%88%E3%81%A7%E9%81%A0%E9%9A%94%E6%93%8D%E4%BD%9C%E3%82%92%E5%8F%AF%E8%83%BD%E3%81%AB%E3%81%99%E3%82%8B)を追加すると、インターネット接続があれば世界中のどこからでもオフィスに取り付けたセサミにアクセスできるようになります。

さらに、セサミは[APIが公開されています](https://docs.candyhouse.co/)ので、独自にカスタマイズすることが可能です。

物理キーではなくスマホアプリで操作できるようになっただけでも、以前よりかなり便利にはなったのですが、やはり理想はICカードのタッチで開け締めできることです。そのためにはWiFiアクセスポイントが欠かせないので追加で購入しました。

# ラズパイとICカードリーダーのセットアップ

ICカードを読み取ってAPIを操作する処理にはRaspberry Piを使います。参考になる記事がすでに存在したので、概ねその手順に沿って進めました。

* [SuicaでSESAME(セサミ)を解錠 【ラズパイ】](https://jp.candyhouse.co/blogs/how-to/suica%E3%81%A7%E3%82%BB%E3%82%B5%E3%83%9F%E3%82%92%E8%A7%A3%E9%8C%A0-with-%E3%83%A9%E3%82%BA%E3%83%91%E3%82%A4)
* [【前編】Raspberry Pi Zero WHを使ってSESAMEのスマートロックをICカードで解錠する](https://www.otwo.jp/blog/raspi_sesame_1/)

まずは必要な機器を揃えます。すべてAmazonで購入しました。

* [Raspberry Pi Zero WHスターターキット](https://www.amazon.co.jp/gp/product/B07GYRVHJ9/)
* [SONY 非接触ICカードリーダー/ライター PaSoRi RC-S380](https://www.amazon.co.jp/dp/B00948CGAG)
* [エレコム USBケーブル miniB 5m ブラック USB-ECOM550](https://www.amazon.co.jp/gp/product/B000XFPZUC/) （※ラズパイとICカードリーダーの接続用。それぞれの設置場所が離れているので長さのあるものを買いました）
* [3M 両面テープ コマンドタブ Sサイズ クリア CM3PS-CL](https://www.amazon.co.jp/gp/product/B00IK86PIK/) （※壁面ガラスにICカードリーダーを貼り付けるために購入）

そして先述の記事の内容に沿ってセットアップを進めたのですが、USBハブやマウス、キーボードは手持ちがなかったので、以下の記事を参考にしてWiFi経由で操作することにしました。

* [Macで楽々RaspberryPi キーボード・ディプレーなしにラズパイを使う](https://qiita.com/makaishi2/items/cabed517a758c60affe4)
* [macしかない状況でRaspberry Piに接続してみた](https://qiita.com/murs313/items/81757bf8bc74b6b76cdf)

上記の手順はセキュリティのことをあまり考慮されていないので、そこらへんはちゃんとしておきましょう。いろんな人が混在するシェアオフィスですしね。

# WeWorkのICカードを読み取ってセサミAPIを操作する

それでまあ、先のラズパイでセサミを操作する記事に従って進めればだいたいOKなのですが、ちょっとそのままでは上手くいかない部分があります。**WeWorkのカードはSuicaじゃない**のと、**記事で使われているAPIのバージョンが古い**せいです。

なので、上手くいかない部分を修正して以下のようなコードを完成させました。なお、現行バージョンのAPIではAPIキーが必要になります。APIキーの取得方法は[こちら](https://jp.candyhouse.co/apps/help-center#!hc-api-1-2)に記載されています。

```python:SesameNFC.py
# -*- coding: utf-8 -*-
import requests
import json
import binascii
import nfc
import time
import traceback
from threading import Thread, Timer

# ICカード待ち受けの1サイクル秒
TIME_cycle = 1.0
# ICカード待ち受けの反応インターバル秒
TIME_interval = 0.2
# タッチされてから次を開始するまでの無効化する秒
TIME_wait = 3

#SESAME API KEY
API_key = "セサミのAPIキー"

# NFC接続リクエストのための準備
# 106A(NFC type A)で設定
target_req_nfc = nfc.clf.RemoteTarget("106A")

print 'ICカードをタッチしてください...'
while True:
    try:
        # USB接続されたカードリーダーをインスタンス化
        clf = nfc.ContactlessFrontend('usb')
        # ICカード待ち受け開始
        # clf.sense( [リモートターゲット], [検索回数], [検索の間隔] )
        target_res = clf.sense(target_req_nfc, iterations=int(TIME_cycle//TIME_interval)+1 , interval=TIME_interval)

        if target_res != None:

            tag = nfc.tag.activate(clf, target_res)

            #IDを取り出す
            idm = binascii.hexlify(tag.identifier).upper()
            print 'NFC detected. ID = ' + idm

            #ICカードのチェック
            if (idm == "tagtool.pyで取り出したID"):
                url_control = "https://api.candyhouse.co/public/sesame/セサミの端末ID"
                head_control = {"Authorization": API_key, "Content-type": "application/json"}
                # get status
                response_control = requests.get(url_control, headers=head_control)
                # 次のリクエストまで間隔が短いとエラーになる？ので数秒待つ
                time.sleep(1)
                res = json.loads(response_control.text)
                stats = res["locked"]

                if (stats == True):
                    # unlock
                    payload_control = {"command":"unlock"}
                    response_control = requests.post(url_control, headers=head_control, data=json.dumps(payload_control))

                else:
                    # lock
                    payload_control = {"command":"lock"}
                    response_control = requests.post(url_control, headers=head_control, data=json.dumps(payload_control))

                print(response_control.text)
                print 'sleep ' + str(TIME_wait) + ' seconds'
                time.sleep(TIME_wait)

            #end if
        #end if

        clf.close()
    #end try

    except KeyboardInterrupt:
        print("Ctrl+Cで停止しました")
        clf.close()
        break

    except:
        clf.close()
        traceback.print_exc()
        pass

#end while
```

これでWeWorkのICカードでセサミの解錠／施錠ができるようになりました！
![sesame02.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/116818/e4cd6157-0e14-2ee4-0de0-dfe35a01bf0a.gif)

ケーブルの挿入口の都合でICカードリーダーのマークが天地逆転してしまったため、WeWorkのステッカーを貼ってごまかしています。結果的にオフィシャルアイテムっぽくなっていて素敵なんじゃなかろうか。

# ICカードタッチや解錠／施錠、エラー時にLEDを点滅させる

とりあえず動くようにはなったのですが、実はこのコードがしばしばエラーで解錠／施錠に失敗します。どうやらAPIかWiFiアクセスポイントの反応が遅れるときがあるようで、真面目にやるならリトライ処理なんかを組み込むべきなんでしょうが、もう一回タッチすればだいたい上手くいくからまあいいか……と手直しせず放置しています。

その代わりに、イベントごとにLEDを点滅させるようにしました。点滅のパターンによってどの処理が行われたのか判断つきますし、何よりラズパイやるならLチカは王道ですし！

LEDを点滅させる方法は以下の記事を参考にしました。
[ラズベリーパイ(Raspberry Pi Zero WH)でPython3 Lチカ(LED点滅)させる方法](https://ma-chanblog.com/raspi-led-blinking/)

そしてLチカを組み込んだコードが以下のとおりです。

```python:SesameNFC_LED.py
# -*- coding: utf-8 -*-
import requests
import json
import binascii
import nfc
import time
import traceback
import RPi.GPIO as GPIO
from threading import Thread, Timer

# ICカード待ち受けの1サイクル秒
TIME_cycle = 1.0
# ICカード待ち受けの反応インターバル秒
TIME_interval = 0.2
# タッチされてから次を開始するまでの無効化する秒
TIME_wait = 3

#SESAME API KEY
API_key = "セサミのAPIキー"

# NFC接続リクエストのための準備
# 106A(NFC type A)で設定
target_req_nfc = nfc.clf.RemoteTarget("106A")

print 'ICカードをタッチしてください...'
while True:
    try:
        # USB接続されたカードリーダーをインスタンス化
        clf = nfc.ContactlessFrontend('usb')
        # ICカード待ち受け開始
        # clf.sense( [リモートターゲット], [検索回数], [検索の間隔] )
        target_res = clf.sense(target_req_nfc, iterations=int(TIME_cycle//TIME_interval)+1 , interval=TIME_interval)

        if target_res != None:

            tag = nfc.tag.activate(clf, target_res)

            #IDを取り出す
            idm = binascii.hexlify(tag.identifier).upper()
            print 'NFC detected. ID = ' + idm

            #ICカードのチェック
            if (idm == "tagtool.pyで読み取ったID"):

                #GPIO番号の指定モードを設定(BCM:役割ピン番号、BOARD:PIN番号)
                GPIO.setmode(GPIO.BCM)
                #23番ピン（緑色LED）を出力として使用するように設定
                GPIO.setup(23, GPIO.OUT)
                # 点滅
                for i in range(3):
                    GPIO.output(23, GPIO.HIGH)
                    time.sleep(0.5)
                    GPIO.output(23, GPIO.LOW)
                    time.sleep(0.5)
                #GPIOの設定をリセット
                GPIO.cleanup()

                url_control = "https://api.candyhouse.co/public/sesame/セサミの端末ID"
                head_control = {"Authorization": API_key, "Content-type": "application/json"}
                # get status
                response_control = requests.get(url_control, headers=head_control)
                # 次のリクエストまで間隔が短いとエラーになる？ので数秒待つ
                time.sleep(1)
                res = json.loads(response_control.text)
                stats = res["locked"]

                if (stats == True):
                    # unlock
                    payload_control = {"command":"unlock"}
                    response_control = requests.post(url_control, headers=head_control, data=json.dumps(payload_control))
                    # 解錠時は緑色LED点灯
                    GPIO.setmode(GPIO.BCM)
                    GPIO.setup(23, GPIO.OUT)
                    GPIO.output(23, GPIO.HIGH)
                    time.sleep(3)
                    GPIO.output(23, GPIO.LOW)
                    GPIO.cleanup()

                else:
                    # lock
                    payload_control = {"command":"lock"}
                    response_control = requests.post(url_control, headers=head_control, data=json.dumps(payload_control))
                    # 施錠時は緑色LED早く点滅
                    GPIO.setmode(GPIO.BCM)
                    GPIO.setup(23, GPIO.OUT)
                    for i in range(6):
                        GPIO.output(23, GPIO.HIGH)
                        time.sleep(0.25)
                        GPIO.output(23, GPIO.LOW)
                        time.sleep(0.25)
                    GPIO.cleanup()

                print(response_control.text)

                print 'sleep ' + str(TIME_wait) + ' seconds'
                time.sleep(TIME_wait)

            #end if
        #end if

        clf.close()
    #end try

    except KeyboardInterrupt:
        print("Ctrl+Cで停止しました")
        clf.close()
        # エラー時赤色LED（22番ピン）を激しく点滅
        GPIO.setmode(GPIO.BCM)
        GPIO.setup(22, GPIO.OUT)
        for i in range(10):
            GPIO.output(22, GPIO.HIGH)
            time.sleep(0.1)
            GPIO.output(22, GPIO.LOW)
            time.sleep(0.1)
        GPIO.cleanup()
        break

    except:
        clf.close()
        GPIO.cleanup()
        traceback.print_exc()
        # エラー時赤色LEDを激しく点滅
        GPIO.setmode(GPIO.BCM)
        GPIO.setup(22, GPIO.OUT)
        for i in range(20):
            GPIO.output(22, GPIO.HIGH)
            time.sleep(0.1)
            GPIO.output(22, GPIO.LOW)
            time.sleep(0.1)
        GPIO.cleanup()
        pass

#end while
```

……とりあえず動くようにしただけの頭の悪いコードですが、業務に支障が出るわけでもないのでやっぱり手直しせずに放置しています。

デスク上でLEDが点滅しているのがおわかりいただけるでしょうか。このときばかりはWeWorkのガラス壁面をありがたいと思いました。普通の壁だったらICカードリーダーやLEDの設置がもっと面倒だったことでしょう。
![sesame03.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/116818/92997034-4afa-052b-7d26-907ecb54960f.gif)

# おわりに

こうして、WeWorkのICカードで部屋の鍵を開け締めしたいという要望を実現することができました。

コードのイケてなさやAPIとWiFiアクセスポイントの反応の遅さのせいで、**解錠／施錠にしばしば失敗**したり、**タッチから解錠／施錠まで数秒かかる**という課題はあるものの、利用する人数や頻度がそれほどでもないのでまあよしとしています。ぶっちゃけ、私が快適ならそれでいいです。

ということで、物理キーやスマホ操作から解放され、ICカードさえあれば共用エリアからプライベートオフィスまで自由に行き来できるようになって大変満足しています。めでたしめでたし。