怠惰になるために本気出す

電子工作で色々作ってます

Sesame解錠編③ Sesame3をWEB APIで解錠

今回はWEB APIを使ってSesameを解錠させます。
全体の概要はこちら
iotblog5296.hatenablog.com

UUIDの確認

セサミ公式アプリに下記の赤枠にUUIDが表示されます。

また、「このセサミの鍵をシェア」を押して表示されるQRコードを画像として保存しておきます。

APIキーの取得

公式サイトからこちらからAPIキーを取得します
https://partners.candyhouse.co/login

リンクに飛んでアドレスを入力し、指定したアドレスに記載してある数字を入力すると


黒塗りしている部分に個人のAPIキーが表示されるのでそれをメモします。


Sesameの鍵の状態(RESTer)

chrome拡張機能を使って動作確認してきます。
Method: GET
URL: https://app.candyhouse.co/api/sesame2/[操作するSESAMEのUUID]
Header:
 Name:x-api-key
 Value: [API_KEY]
上記の[ ]部分は先程メモした
UUIDとAPIキーを入力します(入力する際は[ ]は不要です)

正常に動作したので200が返ってきています。
取得した状態の内容は
batteryPercentage: 電池残量
batteryVoltage: 電池電圧
position: サムターン角度
CHSesame2Status: 状態
timestamp: 更新時刻

statusの箇所で"locked"と表示されており、実際に鍵も施錠しているので一致していました。

正常に動作しない場合、どこかしらのネットが切れている可能性が高いのでWifiモジュールがエラーを起こしていないか、そもそもネット自体切断されていないかなどの確認をしてみてください。


secret_keyの取得

WEB APIを取得したページを開き赤丸部分を押して保存しておいたQRコードの写真を選択します。


すると赤枠部分にsecret_keyが表示されるのでメモします。

sesameのweb apiを取得するページの表示方法が気が付いたら変わっていて、以前は自分でシークレットキーをhexに変換していましたがこちらのページ上で表示されるようになったため不要になりましたがこういう方法もあります

pythonSesameの状態取得

web apiでいけることがわかったので今度はpythonでコードを書いて試していきましょう
コードを書くにあたりライブラリをインストールします。
ターミナルを起動して下のコードを入力します。

$ pip install pycryptodome, requests


インストールが終わったら動かしてみましょう。Thonny python IDEを起動して


下のコードをコピペして各々のUUID、API、シークレットキーを入力してRunします。

import datetime, base64, requests, json
from threading import Thread, Timer
from Crypto.Hash import CMAC
from Crypto.Cipher import AES

uuid = '個人のUUID'
secret_key = '個人のシークレットキー'
api_key = '個人のAPIキー'

# HTTP header
headers = { 'x-api-key': api_key }

# signの生成
cmac = CMAC.new(bytes.fromhex(secret_key), ciphermod=AES)
message = int(datetime.datetime.now().timestamp()).to_bytes(4, 'little', signed=False)[1:4]
cmac.update(message)
sign = cmac.hexdigest()
            
#鍵の状態取得
surl = f'https://app.candyhouse.co/api/sesame2/{uuid}'
sres = requests.get(surl, headers=headers) #リクエスト
print(sres.text)


するとShell部分に先程chrome拡張機能で行った状態と同じものが表示されます。


pythonで解錠処理

状態取得は確認できたので解錠処理も確認していきます。先程と同様に下のコードをコピペしてRunします

import datetime, base64, requests, json
from Crypto.Hash import CMAC
from Crypto.Cipher import AES

uuid = '個人のUUID'
secret_key = '個人のシークレットキー'
api_key = '個人のAPIキー'

# 鍵の操作(toggle/lock/unlock)
cmd = 83    # 88/82/83 = toggle/lock/unlock


# HTTP header
headers = { 'x-api-key': api_key }

# 履歴に残す内容
history = 'open (Web API)' # 半角21文字/全角不明
history = base64.b64encode(history.encode()).decode()

# signの生成
cmac = CMAC.new(bytes.fromhex(secret_key), ciphermod=AES)
message = int(datetime.datetime.now().timestamp()).to_bytes(4, 'little', signed=False)[1:4]
cmac.update(message)
sign = cmac.hexdigest()

# 鍵の操作
url = f'https://app.candyhouse.co/api/sesame2/{uuid}/cmd'

body = {
    'cmd': cmd,
    'history': history,
    'sign': sign
}
res = requests.post(url, json.dumps(body), headers=headers)
print(res.status_code, res.text)


正常に動作するとshellに200が表示されます。


特定のカードをタッチしたときのみアクションを起こす

状態取得するコードと解錠するコード、以前書いたpasoriidmを抜き出すコードを組み込んでいきます。

import datetime, base64, requests, json, binascii, nfc, os, time
from threading import Thread, Timer
from Crypto.Hash import CMAC
from Crypto.Cipher import AES


idm1 = b"お手持ちのICカード1" 
idm2 = b"お手持ちのICカード2" 

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

#NFC接続リクエストのための準備
#212F(Felica)で設定
target_req_ic = nfc.clf.RemoteTarget("212F")
#0003(IC cart)
target_req_ic.sensf_req = bytearray.fromhex("0000030000")


print('ICカードをタッチしてください...')

while True:
    
    #USB接続されたカードリーダーをインスタンス化
    clf = nfc.ContactlessFrontend('usb')
    #ICカード待受開始
    target_res = clf.sense(target_req_ic, iterations=int(TIME_cycle//TIME_interval)+1, interval=TIME_interval)
    
    if not target_res is None:
        tag = nfc.tag.activate_tt3(clf, target_res)
        tag.sys = 3
        
        #IDmを取り出す
        idm = binascii.hexlify(tag.idm)
        print(idm)
        
        #特定のIDmだった場合のアクション
        if idm == idm1 or idm == idm2:
            uuid = '個人のUUID'
            secret_key = '個人のシークレットキー'
            api_key = '個人のAPIキー'

            # HTTP header
            headers = { 'x-api-key': api_key }

            # signの生成
            cmac = CMAC.new(bytes.fromhex(secret_key), ciphermod=AES)
            message = int(datetime.datetime.now().timestamp()).to_bytes(4, 'little', signed=False)[1:4]
            cmac.update(message)
            sign = cmac.hexdigest()
            
            print("【 特定のIDmにより施解錠 】")
            #鍵の状態取得
            surl = f'https://app.candyhouse.co/api/sesame2/{uuid}'
            sres = requests.get(surl, headers=headers) #リクエスト
            print(sres.text)
            
            if "unlocked" in sres.text:
                print("close")
                cmd = 82
                history = 'close (Web API)'

            else:
                print("open")
                cmd = 83
                history = 'open (Web API)'
            history = base64.b64encode(history.encode()).decode() #履歴に「open」or「close」を記載
            url = f'https://app.candyhouse.co/api/sesame2/{uuid}/cmd'
            body = {
                'cmd': cmd,
                'history': history,
                'sign': sign
            }
            res = requests.post(url, json.dumps(body), headers=headers)
            print(res.status_code, res.text)
            print('sleep'+ str(TIME_wait)+ ' second') 
            time.sleep(TIME_wait)
            
    clf.close()
#end while

正常だとshellにこんな感じで表示されます。



土台となるギミックはこれで完成です。
あとは必要に応じてGmailで通知を送ったりブザーを鳴らしたり機能を追加していきます。

次回:エラー時にブザーを鳴らす
iotblog5296.hatenablog.com