2021/10/30 更新
複数台操作の不具合の指摘をうけ、修正がてらプロパティ周りの仕様を大幅変更。
【まえがき:APIが刷新されていた】
SESAME用のAPI* がひっそりと上がっていた(正式発表はまだ?)ので、Google Apps Script からいじれるようにしてみた。
*追記:セサミ3/4/5などに対応。
従来のものとは仕様が変更され、導入のハードルがすこし上がった感じ。
今回は、以前書いたセサミミニ用のスクリプトから乗り換えやすいよう、使用方法を極力継承してセサミ3以降に対応した新しいスクリプトを書いてみた。
【事前準備1:必要な各種データを入手】
1. 公式のユーザーページ(https://biz.candyhouse.co)にログインし、「開発者向け」ページでAPIキーを取得。
2. 以下のいずれかを行う。(追記:現在はB推奨)
A. セサミアプリでマネジャー権限のQRコードを発行し、好みのQRコードリーダーで読み取り、中身のテキスト(ssm://UI? から始まる長い文字列)をコピーしておく。
(以前はSecret Keyなどを簡単に取得できず、このようにするしかなかった)
B. ユーザーページの 「個人で登録済みのデバイス」ボタンから任意のデバイスを選択し、UUIDとSecret Keyを取得。
【事前準備2:外部JavaScriptを追加】
施解錠操作用の署名生成がややこしくて自力では困難なため、artjombさんの cryptojs-extension を拝借する。
1. まずはスクリプトファイルを新規作成
(Googleドライブ 新規→その他→Google Apps Script)
- lib/cryptojs-aes.min.js
- build/cmac.min.js
の内容をそれぞれ丸ごとスクリプトファイルに追加(コピペ)する。
ファイル + からスクリプトを追加できる。
※初期状態で表示されるテキストは不要なので、テキストエリアを空白にしてから貼り付け
名前はなんでも良いけれどわかりやすくそのまま。
【重要】3件のファイルは必ず上記画像の順で配置する。
【汎用サンプルコード】
ようやくメインのコード。
はじめに、「プロジェクトの設定」でChrome V8 ランタイムが有効になっていることを確認。(多分デフォルトでなっている。)
コード.gsに戻り、テキストエリアを空白にしてから以下をコピペ。
const prop = PropertiesService.getScriptProperties();const myKey = prop.getProperty('myKey');const apiKey = prop.getProperty('apiKey');// プロパティ登録 (*****部分を書き換えて初回のみ実行する。実行後は消してもOK)function prepare(){prop.deleteAllProperties();prop.setProperty('myKey', '*****'); // 任意のキー。自由に設定するprop.setProperty('apiKey', '*****'); // ダッシュボードで取得したAPIキー// 事前準備A組は↓に、QRから取得したテキスト全文を入れるanalyzeQR('*****');//analyzeQR('*****'); // デバイスが複数ある場合、適宜追加// 事前準備B組は↓に、セサミに付けた名前, UUID, SecretKey の順で入れるanalyzeQR('*****', '*****', '*****');//analyzeQR('*****', '*****', '*****'); // デバイスが複数ある場合、適宜追加showProps();}//プロパティ確認function showProps(){console.log(prop.getProperties());}// 動作テスト。成功したら200が返り、セサミが動くfunction test() {main(myKey, "セサミの名前", 2, "GAS");/*説明:"セサミの名前" を実際のセサミの名前に変更する"GAS" はアプリの通知と履歴に表示される名前。変更可セサミの名前について補足説明:将来もしアプリ上でセサミの名前を変更したとしても、このスクリプトの運用に影響はありません設定を更新する必要はなく、逆に言えば、prepare()実行時点の名前を使い続ける必要があります
新しい名前をGASでも使いたい場合は、新しい情報で再度prepare()を実行します*/}function doPost(e) {const p = JSON.parse(e.postData.contents);main(p.myKey, p.deviceName, p.command, p.user);}function doGet(e) {const p = e.parameter;main(p.myKey, p.deviceName, p.command, p.user);}// 施解錠操作function main(key, device, command, user='ウェブアプリ') {if(key != myKey) return;const c = [83, 82, 88][parseInt(command)]; // lock:82,unlock:83,toggle:88const h = Utilities.base64Encode(user, Utilities.Charset.UTF_8);const devices = device.split(',');devices.forEach(function(name) {const data = JSON.parse(prop.getProperty(name));const body = {'cmd': c,'history': h,'sign': generateCmacSign(data.secKey)}const options = {headers: {'x-api-key': apiKey},method: 'POST',muteHttpExceptions: true,payload: JSON.stringify(body)}const url = `https://app.candyhouse.co/api/sesame2/${data.uuid}/cmd`;const response = UrlFetchApp.fetch(url, options).getResponseCode();console.log(response);});}// CMAC認証function generateCmacSign(secKey) {const date = Math.floor(Date.now() / 1000);const dateDate = new DataView(new ArrayBuffer(4));dateDate.setUint32(0, date, true);const msg = dateDate.getUint32(0).toString(16).slice(2, 8);const hex = CryptoJS.enc.Hex.parse;return CryptoJS.CMAC(hex(secKey), hex(msg)).toString();}// QR情報デコードfunction analyzeQR(p1, p2, p3){if(p1 == '' || p1.indexOf('*') == 0) return;let name;let data = {};if(p2){ // デコード済みデータが来てるname = p1;data = {'uuid':p2, 'secKey':p3};}else{ // 生データが来てるconst ssm = decodeURIComponent(p1)const params = ssm.slice(ssm.indexOf('?') + 1).split('&');params.forEach(function(p) {if (p.indexOf('sk=') == 0){const sk = p.slice(3);const uuid = `${hx(sk,83,86)}-${hx(sk,87,88)}-${hx(sk,89,90)}-${hx(sk,91,92)}-${hx(sk,93,98)}`;data.uuid = uuid.toUpperCase();data.secKey = hx(sk, 1, 16);}else if(p.indexOf('n=') == 0){name = p.slice(2);}});}prop.setProperty(name, JSON.stringify(data));}// QR情報デコード Corelet hx = (data, start, end) => {return Utilities.base64Decode(data).slice(start, end + 1).map(function(chr){return (chr+256).toString(16).slice(-2)}).join('');}
【使用方法】
【1. プロパティを登録】
まず初回のみプロパティを登録する。
【2. 動作チェック】
- function test() 内の情報を適宜書き換える。
- 関数メニューで「test」を選択し、実行ボタンを押す。
成功したら200が返り、セサミが動く。
うまく行かない場合は…
主な返り値と原因の可能性
404:サーバーダウン OR ルーターやWi-Fiモジュール等の問題かも。
403:APIキーが間違っているかも。
502:UUIDが間違っているかも。
200なのに動作しない:SecretKeyが間違っているかも。
【3. ウェブアプリ化】
途中で「承認が必要」と出た場合は流れに従って承認する。
(ログイン→詳細→安全ではないページに移動→許可)
最後に表示されたウェブアプリURLをコピーしておく。
【4. 運用】
あとは従来同様、このURLに適切にリクエストを送れば良い。
より汎用的にするためPOST、GET両方用意しているけれど、基本的にはPOSTでOK。
パラメータは従来からひとつ増え、myKey、deviceName、command、userの4つ。
- myKey:prepareで準備した任意のキー。一致しないとはじかれる。
- deviceName:セサミの名前。複数台同時に動かしたい場合は、カンマ区切りで「玄関,玄関2,倉庫」のように。
- command:数字で、0~2のいずれか
0=解錠 1=施錠 2=トグル - user:通知や履歴に表示される名前。省略可。省略した場合「ウェブアプリ」となる。
※userに特定の文字列を指定すると一部が文字化けする事象を確認済み。特定の文字というわけでなく、規則性をつかめず。現時点で詳細不明。
【最後に:スクリプトをあとから編集した場合】
スクリプトの編集をウェブアプリに反映させるには再デプロイが必要。
1. 編集を保存
2. 「デプロイ」→「デプロイを管理」
3. 編集ボタン(右上のペンアイコン)を押して、「バージョン」から新バージョンを選択
4. デプロイ実行
以上。
(注意・免責)リスクを管理し、あくまで自己責任で使用してください。
【余談】
今回は施解錠のみのスクリプトのため以上となるけれど、新APIはGETも刷新されていて、履歴やバッテリ電圧、サムターン角度なども取得できるようになっていた。アイデア次第で便利に使えそうだ。