「SwitchBot ハブ3」を用いてサーバー室の空調を自動管理してみた
様々な家電製品と連携可能なスマートリモコンがSwitchBot ハブ3です。今回は実際にGIGAZINEのサーバー室に導入し、空調の自動管理とアラート通知を運用開始してみました。
・目次
◆1:経緯 ◆2:導入 ◆3:自動化 ◆4:開発者オプション ◆5:お試しAPIコール ◆6:n8nとの連携 ◆7:まとめ◆1:経緯
2025年6月10日(火)19時45分過ぎからGIGAZINE編集部周辺で停電が発生しました。原因は樹木や鳥獣等の接触です。停電そのものは短時間で復旧し、編集部員も帰宅している時間帯であったことから、停電による影響はほぼありませんでした。最も懸念すべきサーバー室の機器類についてもUPSやPowerwallからの給電が行われたため、正常に稼働し続けました。そんな中で唯一問題があったのは、サーバー室の室温を維持するために絶え間なく冷却し続けているエアコン。サーバー機器ではなく200Vの電源を要求するエアコンはUPSによる電源供給の対象外であり、また停電状態からPowerwallによる給電に切り替わるには若干のタイムラグがあったものとみられ、そのためエアコンはごく短時間の電源断により運転を停止してしまいました。これにより、正常に稼働し続けるサーバー機器は元気よく排熱する一方で、エアコンによる空調は機能しなくなり、そのためサーバー室の室温は人知れず徐々に上昇し続けることとなりました。
異変が現れたのは停電から丸一日経過した頃で、一部のサーバーからの応答が不安定になり始め、たまたま社内に残っていた編集部員が様子を見に行ったところ、サーバー室が高温になっているだけでなく、サーバー機器の冷却ファンが異常な音を立てて回転していることに気付き、ここから大騒ぎで換気やエアコン起動といった対応を行うこととなりました。
こういったインシデントへの反省を通じ、以下の教訓を得ることとなりました。 ・スマートリモコンをサーバー室に配備し、室温上昇をトリガーとしてエアコンを始動させるようにする必要がある ・室温が一定値を超えた場合に、外部に通知する仕組みを用意する必要がある
ちょうど社内の備品として新しいハブ3が存在しており、室温をトリガーとするオートメーション機能があることに加え、SwitchBotAPIが提供されているため通知の実装が可能であることから、サーバー室への導入を検討していくこととなりました。
◆2:導入
まずは導入作業の手始めとして、サーバー室用のSwitchBotアカウントを新規作成します。レビュー用のアカウントは所持していましたが、互いに影響を及ぼすようなことがあってはならないからです。 次に新規作成したアカウントでSwitchBotアプリにログインしてハブ3を登録しましたが、トラブル発生。登録処理の終盤に、購買履歴のアップロードを求める見慣れない画面が表示されました。何度かデバイスの登録を行った経験上、こういった手順は存在しなかったはずなので疑問を覚えましたが、ハブ3のレビュー時にレビュー用アカウントに登録したままだったことに気付き、レビュー用アカウントから登録削除を行ったところ、無事に解決しました。こうして無事にハブ3の登録が完了したので、サーバー室に配備します。ハブ3自体への給電が停止すると意味がないので、UPSを経由することで確実に給電され続けるようにします。また、停電の原因として地震が多いため、念には念を入れてラックに固定し、揺れて転落して動作が停止する可能性も排除していきます。
◆3:自動化
自動化の目標はサーバー室のリモコンを制御することにあるので、まずはサーバー室に配備したハブ3へ室内に2台あるエアコンのリモコンを学習させ登録します。登録後、オートメーション画面を開き、室温上昇をトリガーとしてエアコンを操作するオートメーションを作成します。「おすすめ」に「お部屋をひんやり涼しく」というサンプルがありますので参考にします。
不要なトリガーを削除し、エアコンを2台操作するように変更します。
万が一オートメーションが作動した瞬間にエアコンへの給電が断たれていた場合に備え、トリガーとなる室温を少し変更して発動タイミングをずらしたオートメーションをもう一つ用意しておきます。
こうして2つのオートメーションを登録できました。これで対応できない事態はそもそも自動化で対処する範疇を超えていますので、オートメーションの設定については一旦ここまでで完了とします。
◆4:開発者オプション 通知機能の連携を実装するにあたり、なるべくトリガーに対するアクションに柔軟性を持たせたいという考えから、n8nを選択しました。ただ、n8nにはSwitchBotのトリガーが標準機能として提供されていないため、APIを利用して連携させる必要があります。 まずは、SwitchBotAPIを呼び出すために必要となる「トークン」と「クライアントシークレット」を、SwitchBotアプリを通じて入手する必要があります。アプリを開き、下部にある「プロフィール」をタップします。
表示されるメニューから「設定」をタップして設定画面を開きます。
設定画面の一番下にある「基本データ」をタップします。
開いた画面で、ロゴのすぐ下に表示される「アプリバージョン」を連続で10回タップします。
「開発者向けオプション」が出現するのでタップします。
「トークン」と「クライアントシークレット」をコピーします。
◆5:お試しAPIコール 「トークン」と「クライアントシークレット」を得られたので、試しにAPIをコールしてみます。APIのドキュメントを見てみると、様々なプログラミング言語のコード例が載っていますが、いずれも根本的な違いはなさそうなので、PHPで組んでみることにします。 まずは、GETメソッドの例として「デバイス一覧」を取得してみます。
<?php
$token = 【トークン】;
$secret = 【クライアントシークレット】;
function guidv4() {
$data = random_bytes(16);
assert(strlen($data) === 16);
$data[6] = chr(ord($data[6]) & 0x0f | 0x40);
$data[8] = chr(ord($data[8]) & 0x3f | 0x80);
return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4));
}
$nonce = guidv4();
$t = time() * 1000;
$data = utf8_encode($token . $t . $nonce);
$s = hash_hmac('sha256', $data, $secret, true);
$sign = strtoupper(base64_encode($s));
$url = 'https://api.switch-bot.com/v1.1/devices';
$headers = array(
"Content-Type:application/json",
"Authorization:" . $token,
"sign:" . $sign,
"nonce:" . $nonce,
"t:" . $t
);
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
$response = curl_exec($ch);
curl_close($ch);
echo $response;
レスポンスは以下のようになりました。
{
"statusCode": 100,
"body": {
"deviceList": [
{
"deviceId": 【ハブ3のデバイスID】,
"deviceName": "エアコンみまもりくん",
"deviceType": "Hub 3",
"enableCloudService": true,
"hubDeviceId": ""
}
],
"infraredRemoteList": [
{
"deviceId": 【エアコン1のデバイスID】,
"deviceName": "AC-①",
"remoteType": "Air Conditioner",
"hubDeviceId": 【ハブ3のデバイスID】
},
{
"deviceId": 【エアコン2のデバイスID】,
"deviceName": "AC-②",
"remoteType": "Air Conditioner",
"hubDeviceId": 【ハブ3のデバイスID】
}
]
},
"message": "success"
}
もう一つ、POSTメソッドの例として「webhookの登録」をしてみます。
<?php
$token = 【トークン】;
$secret = 【クライアントシークレット】;
function guidv4() {
$data = random_bytes(16);
assert(strlen($data) === 16);
$data[6] = chr(ord($data[6]) & 0x0f | 0x40);
$data[8] = chr(ord($data[8]) & 0x3f | 0x80);
return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4));
}
$nonce = guidv4();
$t = time() * 1000;
$data = utf8_encode($token . $t . $nonce);
$s = hash_hmac('sha256', $data, $secret, true);
$sign = strtoupper(base64_encode($s));
$url = 'https://api.switch-bot.com/v1.1/webhook/setupWebhook';
$webhook_url = '【webhookのURL】';
$headers = array(
"Content-Type:application/json",
"Authorization:" . $token,
"sign:" . $sign,
"nonce:" . $nonce,
"t:" . $t
);
$params = array(
'action' => 'setupWebhook',
'url' => $webhook_url,
'deviceList' => 'ALL'
);
$json_params = json_encode($params);
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_POST, TRUE);
curl_setopt($ch, CURLOPT_POSTFIELDS, $json_params);
$response = curl_exec($ch);
curl_close($ch);
echo $response;
レスポンスは以下のようになりました。
{
"statusCode": 100,
"body": {},
"message": "success"
}
◆6:n8nとの連携 準備は整ったので、いよいよn8nのワークフローを作成します。Webhookをワークフロー開始のトリガーとして使用しますが、Webhookが正常に動作しない場合に備え、定期的にAPIを呼び出すトリガーも設定します。ワークフローは以下のようになります。
・スケジュール実行 こちらは「Schedule Trigger」ノードをトリガーとします。「Trigger Interval」を「Custom (Cron)」に設定することで、将来的にスケジュールを柔軟に変更できるようにしておきます。
トリガーが発火したら、認証情報付きのリクエストヘッダーを生成します。これには「Code」ノードを使用し、言語はデフォルトである程度ライブラリが使用可能な「Python」を使用します。
なお、コードはサンプルをほぼそのまま利用してこんな感じです。
import json
import time
import hashlib
import hmac
import base64
import uuid
# Declare empty header dictionary
header = {}
# open token
token = 【トークン】
# secret key
secret = 【クライアントシークレット】
nonce = uuid.uuid4()
t = int(round(time.time() * 1000))
string_to_sign = '{}{}{}'.format(token, t, nonce)
string_to_sign = bytes(string_to_sign, 'utf-8')
secret = bytes(secret, 'utf-8')
sign = base64.b64encode(hmac.new(secret, msg=string_to_sign, digestmod=hashlib.sha256).digest())
header['Authorization'] = token
header['Content-Type'] = 'application/json'
header['charset'] = 'utf8'
header['t'] = str(t)
header['sign'] = str(sign, 'utf-8')
header['nonce'] = str(nonce)
return header
そして、「HTTP Request」ノードでAPIをコールします。リクエストヘッダーには「Code」ノードで生成した値を使用します。
APIからのレスポンスデータはこんな感じ。室温のデータはbody.temperatureなので21.4℃です。
[
{
"statusCode": 100,
"body": {
"version": "V1.3-0.8-0.1",
"temperature": 21.4,
"lightLevel": 2,
"humidity": 53,
"moveDetected": false,
"onlineStatus": "online",
"deviceId": 【ハブ3のデバイスID】,
"deviceType": "Hub 3",
"hubDeviceId": 【ハブ3のデバイスID】
},
"message": "success"
}
]
最後に、レスポンスデータを「Code」ノードで処理し、必要な室温データのみを取り出します。ここではライブラリは使用しないので、使用言語は「JavaScript」とします。
・Webhook WebhookはPOSTメソッドとします。先に作成した「webhookの登録」APIコールを使用して登録を行うのですが、webhookの設定で認証情報を登録する方法が見つからなかったため、「Authentication」を「None」に設定しています。これは今後の改善点です。
送られてくるデータはこんな感じ。室温のデータはbody.context.temperatureなので22.8℃です。
[
{
"headers": 【略】,
"params": {},
"query": {},
"body": {
"eventType": "changeReport",
"eventVersion": "1",
"context": {
"detectionState": "NOT_DETECTED",
"deviceMac": 【ハブ3のデバイスID】,
"deviceType": "Hub 3",
"humidity": 53,
"lightLevel": 2,
"scale": "CELSIUS",
"temperature": 22.8,
"timeOfSample": 1750991646754
}
}
}
]
このデータを、スケジュール実行の場合と同様の方法で「Code」ノードによって処理し、必要な室温データのみを取り出します。
・共通(通知) 2系統のフローから得られたデータについてはそれぞれ「Code」ノードで処理してフォーマットを揃えているので、ここからは共通処理となります。まず、「Filter」ノードで室温が27℃を超えるもののみを残します。これにより、室温が正常の場合は通知処理を行わないようにします。
最後に通知処理について、ここでは「Send Email」ノードを用いてメール送信を行うことにします。
◆7:まとめ 完成したワークフローを有効化すると、データが更新されるなどの特定のタイミングでwebhookが呼び出されており、室温が正常であるためにFilterノードまででフローの実行が止まっていることが確認できました。
このようにして、サーバー室の潜在的なリスクをひとつ軽減することができました。今後もこうした作業を積み重ね、安定したGIGAZINEの提供を目指して日々努めていきます。
そして現時点でのGIGAZINEを支えるサーバー室の様子をYouTubeライブで実況中継中です。親愛なる読者の皆様から、1円でも良いのでここから寄付してもらえると、GIGAZINEサーバーが、ものすごい勢いで動き続けます!GIGAZINEを支えるためのAmazon欲しいものリストもこちらにあります。
GIGAZINEを支えているサーバーをテスト配信中Part3 - YouTube
というわけで、今後ともGIGAZINEをよろしくお願いします。