2021/10/30 更新
複数台操作の不具合の指摘をうけ、修正がてらプロパティ周りの仕様を大幅変更。
【まえがき:APIが刷新されていた】
【事前準備1:必要な各種データを入手】
【事前準備2:外部JavaScriptを追加】
- lib/cryptojs-aes.min.js
- build/cmac.min.js
【重要】3件のファイルは必ず上記画像の順で配置する。
【汎用サンプルコード】
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」を選択し、実行ボタンを押す。
【3. ウェブアプリ化】
途中で「承認が必要」と出た場合は流れに従って承認する。
【4. 運用】
- myKey:prepareで準備した任意のキー。一致しないとはじかれる。
- deviceName:セサミの名前。複数台同時に動かしたい場合は、カンマ区切りで「玄関,玄関2,倉庫」のように。
- command:数字で、0~2のいずれか
0=解錠 1=施錠 2=トグル - user:通知や履歴に表示される名前。省略可。省略した場合「ウェブアプリ」となる。
※userに特定の文字列を指定すると一部が文字化けする事象を確認済み。特定の文字というわけでなく、規則性をつかめず。現時点で詳細不明。




