さなぽんです。
今回は表題の通り、スマートロック「sesame」の鍵を操作してみようかと。
既に機能がある中でAPIを使用するに至った経緯なども含めて書いていきますね。
まずは今回の主役となるsesameの紹介です。
sesame スマートロックとは
色々と機能があって便利そう!
また、使い方などの疑問に対する記事がブログには豊富に上がっているので
困った時には非常に役に立ちそうですね。
今回やりたかったこと
イメージはシェアハウスで鍵を共有する、という体です。
公式サイトには鍵のシェアもできるよ!とあります。
APIでシェアができたらなあ、と思い問い合わせをしてみましたところ
以下のようなお返事が届きました。
現在APIを使って出来る事は以下の通りです。
- セサミに施錠・解錠の命令を送る
- セサミの施錠・解錠ステータスの確認
- セサミのバッテリーレベルの確認
CANDY HOUSE Developer Reference
鍵のシェアについては現在APIでは搭載されておりませんので、アプリにて行っていただく必要がございます。
> 鍵のシェアについては現在APIでは搭載されておりません
んー、残念。。
少ない台数のスマートロックであればそこまで大変ではないですが
複数のシェアハウスで鍵を管理する、他のサイトと連動する、となった場合はちょっと煩いそう。。。
ということで、APIを使用し画面も作ってみよう。
との流れになります。
API実装
ここからは既にsesame購入済、アプリにてwifiポイント設定済みで
sesameが既に動く、という前提で進めていきます。
API Key取得
まずはhttps://my.candyhouse.co:CANDY HOUSE Dashboardからログインし
API Settings画面からAPIを使用する為のAPI Keyを発行し、
API Keyをsesame API用のconfigファイルに記述します。
<?php return [ "Sesame"=> [ // API Url "ApiUrl" => "https://api.candyhouse.co/public/" , // API Key "Authorization" => "発行されたAPI Key" , ] ];
API 実行
スマートロック一覧取得
<?php $http = new Client(); $url = Configure::read('Sesame.ApiUrl') . 'sesames/'; $response = $http->get($url, [], ['headers' => [ 'Authorization' => Configure::read('Sesame.Authorization'), ]]); $list = json_decode($response->body());
取得結果
[ { "device_id": "00000000-0000-0000-0000-000000000000", "serial": "ABC1234567" "nickname": "Front door", }, { "device_id": "00000000-0000-0000-0000-000000000001", "serial": "DEF7654321" "nickname": "Back door", } ]
スマートロック状態取得
スマートロックのデバイスIDをパラメータに入れて取得します。
<?php $http = new Client(); $url = Configure::read('Sesame.ApiUrl') . "sesames/{$スマートロック デバイスID}"; $response = $http->get($url, [], ['headers' => [ 'Authorization' => Configure::read('Sesame.Authorization'), ]]); $isLocked = json_decode($response->body())->locked;
取得結果
{ "locked": true, "battery": 100, "responsive": true }
スマートロック開閉
これもスマートロックのデバイスIDをパラメータに入れて取得します。
リクエストメソッドがpostになります。
問い合わせの回答にある 「1. セサミに施錠・解錠の命令を送る」にあたる箇所となります。
この部分はajaxで処理をする体です。
<?php $http = new Client(); $url = Configure::read('Sesame.ApiUrl') . "sesames/{$スマートロック デバイスID}"; $data = ['command' => $_POST['to_lock'] == 'true' ? 'lock' : 'unlock']; $response = $http->post($url, json_encode($data), [ 'headers' => [ 'Authorization' => Configure::read('Sesame.Authorization'), ], ]); $state = json_decode($response->body());
取得結果
{ "task_id": "01234567-890a-bcde-f012-34567890abcd" }
スマートロック開閉処理結果取得
開閉処理のAPIはあくまでも命令を送るのみなので
その命令で返ってきたtask_idの内容を別のAPI処理で確認します。
スマートロック開閉処理に引き続き行います。
<?php // 処理結果確認 do { $result = $this->getActionResult($taskId); } while (json_decode($result->body())->status != 'terminated'); if (json_decode($result->body())->successful == false) { throw new HttpException(json_decode($result->body())->error); } $status = json_decode($result->body())->status; // ちょっと見辛いですが、do whileのループをprivateメソッド化しています。 private function getActionResult($taskId) { $http = new Client(); $resultUrl = Configure::read('Sesame.ApiUrl') . "action-result?task_id={$taskId}"; $responseResult = $http->get($resultUrl, [], ['headers' => [ 'Authorization' => Configure::read('Sesame.Authorization'), ]]); return $responseResult; }
取得結果
{ "status": "terminated", "successful": true }
view
php
こちらは参考までに。
<div> <div class="loading hide"></div> <div class="container title"> <div class="row"> <div class="col-md-12"> <h3 class="text-center"> <p>スマートキー</p> </h3> </div> </div> </div> <div class="container"> <div class="row"> <div class="col-md-12 text-center"> <p class="error_message" id="error_message" style="display:none;">エラーが発生しました。再度操作してもエラーとなる場合、管理までお問い合わせください。</p> <div class="text-info">下記ボタンをタップして開閉して下さい</div> </div> <div class="col-md-12 text-center"> <?php if ($isLocked) : ?> <button type="button" id="button" class="btn btn-warning btn-circle"> <i class="fas fa-lock"></i><br> <p>しまっています</p> </button> <?php else : ?> <button type="button" id="button" class="btn btn-info btn-circle "> <i class="fas fa-lock-open"></i><br> <p>あいています</p> </button> <?php endif; ?> </div> </div> </div> </div> <script> $('#button').on('click', function() { $.ajax({ type: 'POST', url: '指定のURL', data: { to_lock : "<?= $isLocked ? 'false' : 'true' ?>", }, beforeSend: function(){ $('.loading').removeClass('hide'); }, }) .done( (data) => { console.log(data); window.location.reload(true); }) .fail( (data) => { console.log(data); $('#error_message').css('display', 'block'); $('.loading').addClass('hide'); }) }); </script>
css
cssも参考までに。
.hide { display: none; } .loading { position: fixed; top: 0; right: 0; bottom: 0; left: 0; z-index: 9999; background: rgba(0,0,0,.5); background-image: url(/images/loading.gif); background-repeat: no-repeat; background-attachment: fixed; background-position: center center; background-size: 150px 150px; } .btn-circle { width: 200px; height: 200px; padding: 10px 16px; font-size: 40px; line-height: 1.33; border-radius: 100px; margin-top: 30px; } .btn-circle p { font-size: 14px; color: #fff; margin-top: 10px; } .btn-circle.btn-secondary p { font-size: 14px; color: #575757; margin-top: 10px; }
出来上がり
画面としてはこんな感じです。