月別アーカイブ: 2020年3月

セサミmini、Web Camera でセンサー対応オートロックを作ってみる (PyTorch)

スマートロックの セサミ mini を便利に使っています。(前回の記事) 物理的に鍵を使う必要がなく完全にワイヤレスで済みますし、施錠操作もオートロックのおかげで不要となりました。

非常に気に入っているのですが、セサミ mini のオートロックはドアの開閉と連動しているわけではなく時限式となっています。もたもたしているとドアを閉じる前に鍵が締まってしまうこともあります。時間を長めに設定すると、今度はロックが掛かるまで待つ場合に待ち時間が長くなります。

そこで、セサミの API を使ってドアの開け閉めと連動してロックが掛かる仕組みを作ってみました。本来なら、マグネット式のセンサーを使うのが最も簡単で確実だと思います。今回は手持ちの機材だけで何とかしようと思い、折角なので Web Camera で識別させてみました。

● Sesame API

CANDY HOUSE Developer Reference

さまざまなサイトに解説がありますので参考にさせていただきました。

Sesame API Version 3 のチュートリアル
APIキー取得方法とセサミIDの確認方法

API Key を取得したら Web 呼び出しで操作することができます。さらに鍵自体の Lock / Unlock 操作には、SmartLock 本体を識別するための Device ID が必要です。Device ID は API を通じて一覧から取得することもできますし、上の解説記事を見ると Web のダッシュボードで確認することもできるようです。

以下 Python 3.x を使っています。Device 一覧の取得です。

import requests
import json

API_URL= 'https://api.candyhouse.co/public/'
API_KEY= '<API_KEY>'

result= requests.get( API_URL+'sesames/', headers={ 'Authorization': API_KEY } )
device_list= json.loads( result.text )

for device in device_list:
    print( device['device_id'] )

毎回 Device ID を取得すると時間がかかるので file にキャッシュしておきます。これでサーバーに問い合わせるのは最初の一度だけになります。

import os
import requests
import json

API_KEY= '<API_KEY>'

class SesameAPI:
    CACHE_FILE= 'cache_file.txt'
    API_URL= 'https://api.candyhouse.co/public/'

    def __init__( self ):
        self.load_cache()

    def load_cache( self ):
        if os.path.exists( self.CACHE_FILE ):
            with open( self.CACHE_FILE, 'r' ) as fi:
                self.dev_list= json.loads( fi.read() )
        else:
            result= requests.get( self.API_URL+'sesames/', headers={ 'Authorization': API_KEY } )
            self.dev_list= json.loads( result.text )
            with open( self.CACHE_FILE, 'w' ) as fo:
                fo.write( json.dumps( self.dev_list ) )

    def get_device_id( self ):
        return  self.dev_list[0][ 'device_id' ]


print( SesameAPI().get_device_id() )

Device ID が判明したので、あとは状態の確認や Lock/Unlock コマンドの送信ができます。

class SesameAPI:
    ~

    def get_status( self ):
        result= requests.get( self.API_URL+'sesame/'+self.get_device_id(), headers={ 'Authorization': API_KEY } )
        status= json.loads( result.text )
        return  status['locked']

    def send_command( self, command ):
        requests.post( self.API_URL+'sesame/'+self.get_device_id(),
            headers={ 'Authorization': API_KEY, 'Content-type': 'application/json' },
            data=json.dumps( { 'command': command } ) )


sesame= SesameAPI()

# 状態の取得
print( sesame.get_status() )

# 施錠、解錠
sesame.send_command( 'lock' )
# sesame.send_command( 'unlock' )

Lock/Unlock コマンドの実行は時間がかかるので、結果を確認したい場合はあらためて問い合わせる必要があります。問い合わせには Command 実行時に得られる Task ID を使います。

↓成功か失敗を確実に返す (待つ) 場合。

    def get_task_result( self, task_id ):
        while True:
            time.sleep( 5.0 )
            result= requests.get( self.API_URL+'action-result?task_id='+task_id, headers={ 'Authorization': API_KEY } )
            status= json.loads( result.text )
            if status['status'] == 'terminated':
                if 'successful' in status:
                    return  status['successful']
                return  False

    def send_command( self, command ):
        result= requests.post( self.API_URL+'sesame/'+self.get_device_id(),
            headers={ 'Authorization': API_KEY, 'Content-type': 'application/json' },
            data=json.dumps( { 'command': command } ) )
        task= json.loads( result.text )
        task_id= task[ 'task_id' ]
        return  self.get_task_result( task_id )


sesame= SesameAPI()
print( sesame.send_command( 'lock' ) )

●画像による判定

機械学習 (Deep Learning) を使っています。モデルは非常に簡単な CNN で PyTorch を使いました。

USB の Web Camera を使ってドアの画像を撮影します。かなり少ないですが 640×480 で 1500枚ほど。様々な時間帯で人が写った出入り時の画像も含めます。先に完全に閉じている状態とそれ以外で分類しておきます。

Web Camera の撮影画像は少々暗く、夕方以降は見えないくらい真っ暗な写真が撮れることがあります。ドアを開けた方が明るいので、暗すぎる場合は閉まっているとみなしています。性能が良い最近のスマホの方が明るく写るので、スマホを Web Camera の代わりに使うともっと精度が上がるかもしれません。

学習は 128×128 dot にしましたがもっと小さくても良いと思います。Windows の GeForce GTX1070 でおよそ 15分くらいです。(BatchSize 32, Epoch 1000 の場合)

class Model_Sesame( nn.Module ):

    def __init__( self ):
        super().__init__()
        self.c0= nn.Conv2d( 3, 32, 5 )
        self.c1= nn.Conv2d( 32, 32, 5 )
        self.fc0= nn.Linear( 32*6*6, 128 )
        self.fc1= nn.Linear( 128, 64 )
        self.fc2= nn.Linear( 64, 2 )
        self.drop0= nn.Dropout( 0.25 )
        self.drop1= nn.Dropout( 0.25 )
        self.drop2= nn.Dropout( 0.5 )
        self.drop3= nn.Dropout( 0.5 )

    def forward( self, x ):
        x= F.relu( self.c0( x ) )
        x= F.max_pool2d( x, (4, 4) )
        x= self.drop0( x )
        x= F.relu( self.c1( x ) )
        x= F.max_pool2d( x, (4, 4) )
        x= self.drop1( x )
        x= x.view( x.size(0), -1 )
        x= F.relu( self.fc0( x ) )
        x= self.drop2( x )
        x= F.relu( self.fc1( x ) )
        x= self.drop3( x )
        x= self.fc2( x )
        return  x

開け閉めの判定部分では、OpenCV でキャプチャした画像をおよそ 0.5秒に一度推論に通しています。結果は完全に閉まっているかどうかの二択です。

    def main_loop( self ):
        cap= cv2.VideoCapture( 0 )
        PREDICT_TIME= 0.5
        SLEEP_TIME= 0.2
        STEP_PREDICT= int(PREDICT_TIME / SLEEP_TIME)
        counter= 0
        while( True ):
            ret,image= cap.read()
            cv2.imshow( 'preview', image )
            counter+= 1
            if counter > STEP_PREDICT:
                self.predict( image ) # 判定
                counter= 0
            time.sleep( SLEEP_TIME )
            key= cv2.waitKey(1) & 0xff
            if key == ord('q') or key == ord('\x1b'):
                break
        cap.release()
        cv2.destroyAllWindows()

データが少なく変化も乏しいので過学習している可能性はありますが、開閉のタイミングはそれっぽく取れているようです。あとは状態が変化したタイミングを捉えて、完全に閉まったときだけ lock command を送ります。

長時間走らせていると稀に誤判定が混ざることがあります。施錠されている状態をさらに施錠し直すだけなので、誤判定が紛れ込んでも特に問題はないです。ただそのたびに API が作動したという通知が来てしまうので少々気になります。

そこで確率の差が小さいあやふやな判定はある程度取り除くことにしました。さらに閉じたときは 4回以上連続、開けたときは 2回以上連続で同じ判定が続いたときだけ状態を反映させるようにしています。

    def predict( self, image ):
        size= IMAGE_SIZE # 64
        y,x,ch= image.shape
        ns= min(x,y)
        bx= (x - ns)//2
        by= (y - ns)//2
        crop_image= image[bx:bx+ns,by:by+ns]
        resize_image= cv2.resize( crop_image, (size,size) )
        ndata= np.zeros( (ch,size,size), dtype=np.float32 )
        for iy in (range(size)):
            for ix in (range(size)):
                for ic in range(ch):
                    ndata[ic,iy,ix]= resize_image[iy,ix,ic]
        fdata= ndata * (1.0/255.0)
        fdata= fdata.reshape( (1,ch,size,size) )
        x_data= torch.tensor( fdata, dtype=torch.float32, device=self.device )
        outputs_c= self.model( x_data ).to( 'cpu' ).detach().numpy() # 推論
        oy= outputs_c[0]
        self.sum= (self.sum + oy) * 0.5
        result= np.argmax(self.sum) # 0=Open, 1=Close

	open_state= self.open_state
        if result == self.prev_result:
            self.lock_count+= 1
            if result == 0:
                if self.lock_count >= 4:
                    self.lock_count= 0
                    open_state= 0
            elif result == 1:
                if self.lock_count >= 2:
                    self.lock_count= 0
                    open_state= 1
        else:
            self.lock_count= 0
            self.capture( image ) # 状態変化時の画像の保存
        self.prev_result= result

        if open_state != self.open_state:
            if open_state == 0:
                self.send_command( 'lock' ) # コマンドの送信
        self.open_state= open_state

Web Camera を設置して main_loop() を回します。これでドアを閉じたときに連動してセサミで施錠出来るようになりました。誤判定もなくなり明るい時間帯は安定して動いています。

まだまだデータの蓄積や改良が必要なので、判定結果が変化した時の画像を次の学習のために保存しています。最適化したら Raspberry Pi や Jetson Nano あたりで動かしたいと思っています。

●まとめ

API を利用して セサミ mini でもドアの動きに連動したオートロックを実現することができました。API が使えるのは非常に大きなメリットです。無い機能も簡単に作れますし、他にも様々な応用ができそうです。

最初にも書いたのですが、実用性を考えるなら普通にマグネットスイッチなどのセンサーを使った方が良いと思います。Web Camera を使う利点としては防犯カメラを兼用出来ることくらいでしょうか。もともと学習用データ収集のつもりだったのですが、ドアの開閉に連動して出入りした人の写真がきれいに残るようになりました。

関連エントリ
セサミmini、スマートロックを使って1年
Android UserLAnd で PyTorch を使う。C++ API
RADEON (ROCm) で PyTorch を使う。C++ API
Jetson Nano で TensorFlow の C 言語 API を使う

セサミmini、スマートロックを使って1年

通勤で定期券の代わりになったり財布の代わりになったりスマートフォンの役割が増えています。全く同じように、スマートロックを使うとスマートフォンを家の鍵として使えるようになります。ドアの前でいちいち鍵(物理鍵)を取り出す必要がなくなり、スマートフォン一つで事足ります。

暗闇で鍵穴を探さなくて良くなりますし、オートロックがあるのでいちいち鍵を締める必要もありません。開閉は記録が残るし通知も来ます。どこでも状態を確認できるので、施錠し忘れを気にすることもなくなります。

スマートロックの セサミmini を使い始めてからちょうど 1年になりました。いくつかトラブルにも遭遇しましたが、自分にとってはなくてはならないものとなっています。

CANDY HOUSE: SESAME mini

スマートロックにも様々なものがありますが、自分で後付できるタイプが一番お手軽です。サムターンと呼ばれる一般的な鍵タイプであれば、ドアのつまみに重なるように本体を取り付けるだけで使えるようになります。設置も両面テープを使うので、工事も特殊な工具も要りません。

スマートロックを取り付けてもこれまで通りの使い方もできます。中からは手で鍵を開けられますし、外から普通に物理鍵も使えるので操作方法は好みで組み合わせられます。

●スマートロック セサミmini でできること一覧

・スマートフォンを鍵にする
・AppleWatch を鍵にする
・オートロック
・合鍵を簡単に作ることが出来る
・解錠&施錠ログの記録と確認
・手ぶら解錠、ノック解錠(iPhone のみ)

さらに別売りの WiFi アクセスポイントを使えば

・どこにいても鍵の操作ができる
・施錠状態をどこにいてもモニターできる
・解錠&施錠が行われたときのリアルタイム通知
・音声を使った鍵の開け締め
・スマートスピーカーとか他のデバイスとの連携

など

●スマートフォンを鍵として使う

初期設定が終われば Sesame アプリを入れた iPhone や Android 端末を鍵として使うことができます。ドアの前でアプリを開き、状態が表示されたらタップするだけです。すぐドアのつまみ(サムターン)が回り始めます。

Sesame
↑この画面をタッチするだけで施錠&解錠ができる。

仕組みは Bluetooth による直結です。ドアのそばでアプリを開くと Bluetooth でセサミ本体を検索し繋がります。接続も予想よりも早く、iPhone X の場合 2~3秒ですぐ操作できる状態になります。動作も安定しており iPhone X を使う上では今の所トラブルには遭遇したことがありません。

ただ上に書いたとおり操作にはアプリを使います。アプリを開くにはスマートフォンのロックも解除しなければならないので、若干の操作と時間がかかります。オフィスビルにあるようなタッチだけで即座に反応するカードキーを想像してしまうと少々ものたりないかもしれません。鍵を開けるまでの時間はスマートロックに置き換えても短くならないので注意です。

まだ試したことがないのですが、セサミ mini には「手ぶら解錠」や「ノック解錠」という機能があるので、工夫すればもっと早く開けられる方法もあるかもしれません。

スマートロックで便利になったところはいろいろあります。例えばアプリを入れるだけで他のスマートフォンからも操作できるようになりますし、ユーザーを追加して複数人での鍵のシェアもできます。自分で好きなだけ合鍵を作ることができるわけです。

個人的に一番魅力を感じているのは持ち物を減らせることです。鍵をすぐ取り出せるところに携帯しなくて良くなりますし、スマートフォンか Apple Watch さえ忘れなければ外出してもとりあえずなんとかなります。(ただし保険として物理鍵の携帯も忘れずに)

またオートロックも非常に便利な機能です。

●かなり便利なオートロック

セサミ mini にはオートロック機能があります。鍵を開けてから指定時間経過すると自動的に鍵を締めてくれます。例えば 10秒に設定しておけば、解錠してから 10秒後に自動的に鍵がかかります。必要な操作は「鍵を開けること」だけになります。

◎外に出る場合
・手でサムターンを回して解錠
・オートロックで施錠

◎中に入る場合
・スマートフォンや Apple Watch で解錠
・オートロックで施錠

なお、セサミ mini にはドアの開閉センサーがついていないため、ドアが本当に閉まっているどうかに関わらずロックしようとします。例えば来客対応時など、ドアが開いたままでも一定時間経てばサムターンが回り始めます。

ちなみに同様のスマートロック製品である Qrio にはドアの開閉センサーがついています。こちらは開けっ放しでオートロックが発動することもなく、実際にドアを閉めたタイミングでロックがかかります。その代わり Qrio の実売価格は セサミ mini の倍くらいするので、このあたりの機能差も価格に反映されているのかもしれません。

Qrio Lock

●Apple Watch を鍵として使う

スマートウオッチである Apple Watch からも操作できます。母艦のスマートフォンを経由しているわけではなく、Bluetooh でセサミ本体と直結しているようです。そのため iPhone を持ち歩かなくても良く、Apple Watch 単体でも鍵として機能します。iPhone のバッテリーが切れた場合の予備にもなります。

使い方は本体右のサイドボタンを押して、ドックの中から Sesame を選ぶだけ。

↓右ボタンを押してこの画面がでる
Sesame

↓Sesame を選んで、この画面になったら接続できたらタップするだけ。
null

ドックに登録すると常駐しているらしくアプリの起動で待たされることはありません。すぐ Bluetooth 接続に移行します。接続そのものは iPhone より若干時間がかかりますが、繋がったあとの反応は速いです。使っている Apple Watch がかなり古い Series 2 なので、新しいモデルだと接続も速くなっているかもしれません。

また文字盤のボタンとして登録しておくと、サイドボタンの Dock から選ばなくてもワンタッチで呼び出せます。(下記画像の左下)

Sesame

スマートウォッチだと身につけていられるので忘れることが少なく、かばんやポケットからスマートフォンを取り出す必要もなくなります。その気になれば電車も支払いも家の鍵も Apple Watch だけで済ませることが可能です。

ただスマートウォッチは片手がふさがっていると操作しづらいので、荷物を持っている場合はスマートフォンを取り出した方が早いです。

●Apple Watch Series 2 を使った場合のトラブルと解決方法

実際に Apple Watch をメインの鍵として、昨年 2019年3月から長いこと使用していました。機種は Apple Watch Series 2 で、3年以上前に発売されたモデルです。

iPhone X より若干遅いものの十分期待通りに動いており操作性も悪くありません。ただごく稀に、Bluetooth 接続に失敗して接続しなくなることががありました。もちろんスマートフォンでも開けられるので、締め出されて入れなくなることはありません。この症状は Apple Watch の再起動で直ります。頻度は 1~2ヶ月に一度程度なのであまり問題ではありませんでした。

ところがその後 2019年11月頃に Series 2 用の WatchOS 6 がリリースされ、OS を更新したタイミングで困ったことが起こるようになります。なぜか Sesame の設定が消えてしまい、接続できなくなる場合があります。これが割と頻繁に発生していました。

↓この画面になり設定が消えている。いつの間にか復活している。
Sesame

いろいろ試したところ、ドックに入れているアプリが多いことが原因らしいことがわかりました。

iPhone の Watch アプリを開き、「Dock」->「よく使う項目」に入っているアプリの数をできるだけ減らしておきます。「Sesame」込で 3個程度まで減らした現在は問題も解消し、安定してつながるようになりました。

おそらくドックに登録したアプリはある程度メモリに常駐しているものと思われます。Apple Watch Series 2 は RAM 容量が少ないので、メモリ消費量が多いと動作が不安定になっていたのかもしれません。Watch OS 6 への更新によって OS の消費メモリ量が増加したか、またはデフォルトでドックに入るアプリが増えていた可能性があります。 文字盤にセサミのアイコンを登録していたことが原因だった可能性があります。

下記のように Series 3 で RAM 容量が 1.5倍、Series 4 以降は 2倍に増えているので、新しい世代の Apple Watch では特に問題がないかもしれません。

発売年 名称 CPU RAM
2015年 Apple Watch 無印 armv7a 32bit x1 512MB
2016年 Apple Watch Series 1 armv7a 32bit x2 512MB
2016年 Apple Watch Series 2 armv7a 32bit x2 512MB
2017年 Apple Watch Series 3 arm64 64bit x2 768MB
2018年 Apple Watch Series 4 arm64 64bit x2 1GB
2019年 Apple Watch Series 5 arm64 64bit x2 1GB

Wikipedia: Apple Watch

●Wi-Fi アクセスポイントと API

セサミ mini には別売りのオプションとして「Wi-Fi アクセスポイント」があります。これを設置しておくとセサミ mini でできることが一気に増えます。

「Wi-Fi アクセスポイント」は電源用 USB コネクタ直付けの非常に小さいアダプタです。セサミ mini 本体には Bluetooth しか搭載されていませんが、このアクセスポイントを通して Wi-Fi 接続できるようになります。ルーター経由でインターネットにつながるわけです。

Bluetooth の届かない範囲でも鍵の操作が可能になりますし、通知もリアルタイムで受け取れます。どこにいても鍵の状態がわかるので、戸締まりしたかどうか心配することもなくなります。

またインターネットに繋がると API を通した連携ができるようになります。スマートスピーカーから制御したり、スマートフォン単体でも Siri を使った音声操作が可能となります。iPhone のショートカット機能を使ったスクリプトの作成もできます。

Sesame
↑ショートカットへの登録

API を活用すれば、Qrio のようなドアの開閉に連動したオートロックも自作できそうです。
(2020/03/31 追記: ドアの開閉に連動したオートロックを実際に作ってみました)

●締め出しと物理キーの重要性

一度だけ鍵が開かなくなるトラブルに遭遇しました。朝は普通にオートロックで施錠していたので、その後何らかの原因で故障したのではないかと思われます。セサミ本体のバッテリー残量表示も 100% だし Bluetooth の接続も問題ないのですが、いくら操作してもモーターが回りませんでした。

締め出しをくらったのですが、保険として物理キーも携帯していたためなんとかなりました。初期の頃から Apple Watch の接続に不安定なところがあったので、念の為予備の鍵を持ち歩いていたことが功を奏しました。スマートロックを設置した場合も、予備として物理キーを持っておくことを強くおすすめします。

その後本体は保証期間内だったこともあり交換対応していただきました。サポートの対応は非常に良く、その日のうち(10分経たず)に返答があったことには驚きました。数日使えないだけでも不便を感じていたので大変助かりました。

●まとめ

かなり便利に活用しています。外出時に必要な機能をスマートフォンやスマートウォッチに集約できるのが良いです。

ただし外出時は物理鍵の携帯を強く推奨します。あくまで予備なので、いざというときのために鞄の奥に忍ばせておいたり小銭入れに入れておく程度でも十分だと思います。