スマートコンセントをクラウドを使わずLANから制御してみた

最近のIoTブームでクラウドから制御できるコンセント(スマートプラグとかスマートコンセント)が安価に入手できるようになりました。どれもおおむね、専用のスマートフォンAppや、Amazon AlexaとかGoogle Home、IFTTTに対応しており、音声制御や自動化が可能になっています。それだけでも便利なのですが、LANに閉じて使いたいときもあるよね、ということで、クラウドに繋がらない状態でも制御できないかやってみました。
(何となく第三者のシステムから家庭内の機器を制御できる状態にしておきたくないとか、クラウドサービスが停止したら使えなくなるのがいやだという理由ですが。)

使ったもの

今回買ったのはこの二つ。Amazonのタイムセールで一番安かったので。

Wifiスマートプラグ スマートコンセント スマートソケット [2個セット]

スマートコンセント WiFiパワーストリップサージプロテクタ 電源タップ

後者の方は、USB電源2つ同時のON/OFFも制御することが可能です。

セットアップ

セットアップはSmartHomeというアプリを使って行い、Bluetooth経由で接続先のWi-Fi(2.4GHzのみ)の情報、およびクラウドやLANから制御するための暗号キーがコンセントに設定されます。

接続先を調べてみたところ、この製品は Tuya Smart というクラウドサービスを利用しているようです。また、常時クラウドにMQTTで接続しているものの、LAN内からも制御を受け付けており、スマホアプリも同一LANにある(UDPポート6666番のブロードキャストを見ることで検出可能)場合はLAN内で直接通信をしているようです。

Tuyaを利用しているデバイスは、有志が開発したNode.jsでLANから制御するためのNode.jsのライブラリがありますので、これを使ってLAN内から制御してみることにしました。この方法であれば、インターネットとの通信が遮断された環境や、MQTTがブロックされた状態であっても、制御が可能になります(ただし設定時は(少なくとも同一のSSID/パスワードで)インターネット接続可能なWi-Fiは必要ですが。)
github.com


セットアップする際、スマホから設定されるキーを取得してやる必要があります。キーはHTTPSで暗号化されてクラウドから渡されるので、少々面倒ですが、Tuyaapiのセットアップ手順通り、Charles というHTTPSプロキシを使ってスマホの通信内容を解析してキーを取り出しました。Charlesのセットアップとスマートフォンに独自CA証明書を信頼させる手順は以下が詳しく参考になります。このiPhoneAndroidの手順を踏めばOKです。

通信系のデバッグには Charles が便利

HTTPS通信の内容が取得できるようになったことを確認の上で、スマホアプリから設定操作をやり直すと、キーが取得できます。

制御してみる

あとは、tuyapi を導入し、

npm install tuyapi

単体のプラグであれば smart_plug.js として

#!/usr/bin/env node
const TuyaDevice = require('tuyapi');
setTimeout(() => { process.exit(1) }, 10000);
// 取得したID/キーを以下に設定
var tuya = new TuyaDevice({
  id: 'xxxxxxxxxxxxxxxxxxxx',
  key: "xxxxxxxxxxxxxxxx",
});
// IPアドレスを検出
tuya.resolveIds().then(() => {
  // 引数に応じてON/OFFを設定
  tuya.set({set: process.argv[2] == '1'}).then(result => {
    console.log(result ? 'OK.' : 'failed.');
  });
});

killtimer.unref();

というコードを書いて

./smart_plug.js 1    # ON
./smart_plug.js 0    # OFF

とコマンドを実行することでコンセントのON/OFFができます。

ちなみに、上記ではブロードキャストを受けてIPアドレスを探すため、若干待ちが発生しますが、IPがわかっているならば指定しておくことで tuya.resolveIds() を飛ばして高速化も可能です(以下参照)。

複数のコンセントがあるタップであれば、 smart_tap.js として

#!/usr/bin/env node
const TuyaDevice = require('tuyapi');

var t = setTimeout(() => { process.exit(1) }, 10000);

var tuya = new TuyaDevice({
  id: 'xxxxxxxxxxxxxxxxxxxx',
  key: "xxxxxxxxxxxxxxxx",
  ip: "192.168.xx.xx",  // IPを指定することで高速化
});
// 現在の設定を取得
tuya.get({schema: true}).then(result => {
  console.log(result)
});
// 引数に応じてON/OFFを設定
tuya.set({dps: process.argv[2], set: process.argv[3] == '1'}).then(result => {
  console.log(result ? 'OK.' : 'failed.');
});

t.unref();

というようなコードを書けば、

./smart_tap.js 1 1    # 1番のコンセントをON
./smart_tap.js 1 0    # 1番のコンセントをOFF
./smart_tap.js 5 1    # USB電源のON
./smart_tap.js 6 0    # すべての電源をOFF

などとコマンドを叩くことでLANから制御が可能です。
(IPがわからない・不定の場合は上のコードのように tuya.resolveIds().then(() => { ... }) 内で get, set を行う必要があります。)


ちなみにおまけとして、上記のコマンドを HomeBridge から実行できるようにするか、 homebridge-tuyapi(1つのコンセントのみ対応) を使えば、Siriから制御することも可能です。