はじめに
ボタンにショートカットなどの操作を自由に割り当てられる、Logicoolの「MX Creative Console」を頂き、色々と試している。
特に「ワンタッチでPCをスリープできる」のはとても便利。
デスクライトも同じようにボタン操作できたらもっと便利になると感じた。
現在デスクライトはSwitchBot社のスマートプラグにつなげてあり、Amazon Echo Show経由で音声操作している。
調べると、SwichBot社が公式API提供していたので、これを使って、スマートプラグのON/OFFをPCから制御できるようにしてみる。
準備
SwitchBotAPIの準備
SwitchBotAPIを使用するのに「トークン」と「クライアントシークレット」が必要。
これらは公式の手順を参考に、SwitchBotアプリから取得する。
- SwitchBotアプリを開く -> 「プロフィール」を開く -> 「基本データ」を開く
- 「アプリバージョン」欄を10回程度タップする
- 「開発者向けオプション」が表示されるので、開く
- 「トークン」と「クライアントシークレット」を取得できるので、コピーする
TypeScriptのプロジェクトを作る
勉強も兼ねて、TypeScriptでAPIを操作するバイナリを作ってみる。
参考:https://qiita.com/oharu121/items/3aadafdd5daa8dc53c64
- プロジェクトを作成
npm init -y
- Typescriptをインストールする
npm install typescript ts-node @types/node --save-dev
- tsconfig.jsonを作成する
npx tsc --init
- バイナリ化するためのパッケージを追加
npm install --save-dev pkg
- package.jsonにbinとpkgの設定を追加
"bin": "dist/index.js", "pkg": { "scripts": "dist/index.js", "targets": ["node18-win-x64"], "outputPath": "release" }
[メモ]使い方
- src下にtypescriptのコードを用意する(index.ts)
- jsコードに変換する
npx tsc .\src\index.ts --outDir dist
- exeファイルに変換する
npx pkg .
- release下にexeファイルが出来るので、それを実行する
[メモ]スクリプトに追加する
- package.jsonにスクリプトを追加する
"scripts": {
"build": "npx tsc .\\src\\index.ts --outDir dist && npx pkg ."
},
npm run build
でバイナリ生成まで一気にできる
バイナリ作成
Step1. デバイス一覧の取得(動作確認)
まずは、SwitchBotのデバイス一覧が取得できるかを確認する。
SwitchBotAPIのREADMEにJavaScriptのサンプルコードがあるが、これはデバイス操作のコードだったので、以下の記事のコードを使って動かす。
TypeScriptとNode.jsを使ってSwitchBotのDeviceListを確認する(リクエストの署名)
npm install uuid
npm install --save-dev @types/uuid
import * as crypto from "crypto";
import * as https from "https";
import { v4 as uuidv4 } from "uuid";
const token: string = "YOUR_TOKEN";
const secret: string = "YOUR_SECRET";
const t = Date.now();
const nonce = uuidv4();
const date = token + t + nonce;
const sign = crypto.createHmac("sha256", secret).update(date).digest("base64");
const host: string = "api.switch-bot.com";
const path: string = "/v1.1/devices";
const postOptions: https.RequestOptions = {
host: host,
path: path,
method: "GET",
port: 443,
headers: {
"content-type": "application/json",
authorization: token,
sign: sign,
t: t,
nonce: nonce,
},
};
function getDevices(getOptions: https.RequestOptions): Promise<any> {
return new Promise((resolve, reject) => {
const req = https.request(getOptions, (res) => {
let queue: Buffer[] = [];
res.on("data", (chunk) => {
queue.push(chunk);
});
res.on("end", () => {
const data = Buffer.concat(queue);
resolve(data.toString());
});
});
req.on("error", (e) => {
console.log(`request error: ${JSON.stringify(e)}`);
reject(e);
});
req.end();
});
}
(async () => {
try {
const result = await getDevices(postOptions);
JSON.parse(result).body.deviceList.forEach((devices: any) => {
console.log(`devices:${JSON.stringify(devices)}\n`);
});
JSON.parse(result).body.infraredRemoteList.forEach(
(infraredRemote: any) => {
console.log(`infraredRemote:${JSON.stringify(infraredRemote)}\n`);
}
);
} catch (e) {
console.log(`error:${JSON.stringify(e)}`);
}
})();
Step2. プラグをON/OFFする
デスクライトは、SwitchBotのスマートプラグに繋いでいる。
スマートプラグはSwitchBotAPIのdevices/{deviceId}/commands
にコマンドを送ることで操作できる。
deviceIdはStep1で取得したdeviceListで確認できる。
SwitchBot公式ドキュメントによると、スマートプラグの操作は以下。
-
command
にturnOn
を指定するとプラグがON(点灯) -
command
にturnOff
を指定するとプラグがOFF(消灯)
import * as crypto from "crypto";
import * as https from "https";
import { v4 as uuidv4 } from "uuid";
const token: string = "YOUR_TOKEN";
const secret: string = "YOUR_SECRET";
const deviceId: string = "YOUR_DEVICEID";
const t = Date.now();
const nonce = uuidv4();
const date = token + t + nonce;
const sign = crypto.createHmac("sha256", secret).update(date).digest("base64");
const host: string = "api.switch-bot.com";
const path: string = `/v1.1/devices/${deviceId}/commands`;
const body = {
command: "turnOn", // turnOnとturnOff
parameter: "default",
commandType: "command",
};
const postOptions: https.RequestOptions = {
host: host,
path: path,
method: "POST",
port: 443,
headers: {
"content-type": "application/json",
authorization: token,
sign: sign,
t: t,
nonce: nonce,
},
};
function controlDevice(
options: https.RequestOptions,
body: object
): Promise<void> {
return new Promise((resolve, reject) => {
const req = https.request(options, (res) => {
res.on("end", () => {
resolve();
});
res.resume();
});
req.on("error", (e) => {
console.log(`request error: ${JSON.stringify(e)}`);
reject(e);
});
req.write(JSON.stringify(body));
req.end();
});
}
(async () => {
try {
await controlDevice(postOptions, body);
console.log(`command sent successfully.`);
} catch (e) {
console.log(`error:${JSON.stringify(e)}`);
}
})();
Step3. プラグの状態を取得する
Step1で利用している全デバイスの一覧を取得したが、deviceIdを指定することで、そのデバイス単体の状態を取得することができる。
/v1.1/devices/{deviceId}/status
スマートプラグの現在の状態(ON/OFF)もpower
に入っている。
import * as crypto from "crypto";
import * as https from "https";
import { v4 as uuidv4 } from "uuid";
const token: string = "YOUR_TOKEN";
const secret: string = "YOUR_SECRET";
const deviceId: string = "YOUR_DEVICEID";
const t = Date.now();
const nonce = uuidv4();
const date = token + t + nonce;
const sign = crypto.createHmac("sha256", secret).update(date).digest("base64");
const host: string = "api.switch-bot.com";
const path: string = `/v1.1/devices/${deviceId}/status`;
const postOptions: https.RequestOptions = {
host: host,
path: path,
method: "GET",
port: 443,
headers: {
"content-type": "application/json",
authorization: token,
sign: sign,
t: t,
nonce: nonce,
},
};
function getDevices(getOptions: https.RequestOptions): Promise<any> {
return new Promise((resolve, reject) => {
const req = https.request(getOptions, (res) => {
let queue: Buffer[] = [];
res.on("data", (chunk) => {
queue.push(chunk);
});
res.on("end", () => {
const data = Buffer.concat(queue);
resolve(data.toString());
});
});
req.on("error", (e) => {
console.log(`request error: ${JSON.stringify(e)}`);
reject(e);
});
req.end();
});
}
(async () => {
try {
const result = await getDevices(postOptions);
const json = JSON.parse(result);
console.log(`deviceInfo: ${JSON.stringify(json)}`);
console.log(`status: ${json.body.power}`);
} catch (e) {
console.log(`error:${JSON.stringify(e)}`);
}
})();
応答結果
deviceInfo: {"statusCode":100,"body":{"version":"V2.0","power":"on","deviceId":"*****","deviceType":"Plug","hubDeviceId":"*****"},"message":"success"}
status: on
status
がon
なら点灯、off
なら消灯状態だった。
Step4. プラグの状態に応じて動作を切り替える(トグル制御)
デスクライトのON・OFF用にそれぞれ別々のバイナリを作り、ボタン2つに割り当てる方法もあるが、
プラグの状態をGETして、「ON」なら「OFF」に、「OFF」なら「ON」に切り替えるコードを書き、ボタン1つでデスクライトの操作が完結できるようにする。
import * as crypto from "crypto";
import * as https from "https";
import { v4 as uuidv4 } from "uuid";
// SwitchBot 認証情報
const token: string = "YOUR_TOKEN";
const secret: string = "YOUR_SECRET";
const deviceId: string = "YOUR_DEVICEID";
const host: string = "api.switch-bot.com";
const path: string = `/v1.1/devices/${deviceId}/status`;
// ヘッダー生成
function buildAuthHeaders() {
const t = Date.now();
const nonce = uuidv4();
const date = token + t + nonce;
const sign = crypto
.createHmac("sha256", secret)
.update(date)
.digest("base64");
return {
t,
nonce,
sign,
headers: {
"content-type": "application/json",
authorization: token,
sign: sign,
t: t,
nonce: nonce,
},
};
}
// プラグのステータスを取得する関数
function getPlugStatus(): Promise<"on" | "off"> {
return new Promise((resolve, reject) => {
const { headers } = buildAuthHeaders();
const options: https.RequestOptions = {
host: host,
path: `/v1.1/devices/${deviceId}/status`,
method: "GET",
port: 443,
headers: headers,
};
const req = https.request(options, (res) => {
let queue: Buffer[] = [];
res.on("data", (chunk) => queue.push(chunk));
res.on("end", () => {
const result = JSON.parse(Buffer.concat(queue).toString());
const power: "on" | "off" = result.body.power;
resolve(power);
});
});
req.on("error", (e) => reject(e));
req.end();
});
}
// プラグを操作する関数
function controlPlug(command: "turnOn" | "turnOff"): Promise<void> {
return new Promise((resolve, reject) => {
const { headers } = buildAuthHeaders();
const body = JSON.stringify({
command: command,
parameter: "default",
commandType: "command",
});
const options: https.RequestOptions = {
host: host,
path: `/v1.1/devices/${deviceId}/commands`,
method: "POST",
port: 443,
headers: {
...headers,
},
};
const req = https.request(options, (res) => {
res.on("data", () => {});
res.on("end", () => resolve());
});
req.on("error", (e) => reject(e));
req.write(body);
req.end();
});
}
const postOptions: https.RequestOptions = {
host: host,
path: path,
method: "GET",
port: 443,
headers: {},
};
(async () => {
try {
const status = await getPlugStatus();
console.log(`status: ${status}`);
const command = status === "on" ? "turnOff" : "turnOn";
console.log(`command: ${command}`);
await controlPlug(command);
console.log("command sent successfully.");
} catch (e) {
console.log(`error:${JSON.stringify(e)}`);
}
})();
MX Creative Consoleでバイナリを動かす
Logi Options+で、作成したバイナリを実行するボタンを用意する。
- 「システムアクション」->「アドバンス」->「実行」をボタンに割り当てる
- 実行するプログラムに、生成したバイナリを指定する
これで、ボタンを押すだけで、SwitchBotのスマートプラグ=デスクライトのON/OFFを操作できるようになった。