Cloud Watch Alarmと連携させて自宅のミラーボールを光らせる。

Cloud Watch Alarmと連携させて自宅のミラーボールを光らせる。

2025.09.13

はじめに

皆様こんにちは、あかいけです。

最近部屋の雰囲気を変えたくなり、気分転換にミラーボールを購入しました。
キラキラと光る様子に一時的に癒されたものの、気がつけば部屋の片隅で静かに佇む置物と化していることに気づきました。

「せっかく買ったのに、このままではもったいない…。」

そんな思いから、Cloud Watch Alarmを活用してミラーボールに新たな役割を与えてみることにしました。

また本ブログのコードは以下リポジトリに格納しているので、お気軽にご利用ください。

https://github.com/Lamaglama39/mirror-ball-alarm

モチベーション

しかし何故 Cloud Watch Alarm で自宅のミラーボールを光らせる必要があるのでしょうか。
普通に意味がわかりません。

しかし一見意味不明なこの行為にも、以下の場面で有効だと私は考えています。

監視

まず思いつくのは監視での活用です。

例えばCloud Watch Alarmで死活監視やリソース監視を行っていると思いますが、アラートはどこに連携されるでしょうか?
大抵の場合はメールやSlackやTeams、また緊急性が高い場合は電話対応が一般的かと思います。

それをミラーボールに置き換えるのです。

するとどうでしょう。
あの煩わしい電話から解放され、代わりに愉快なミラーボールが光るわけです。

きっと二回目から光らせたくなくなるでしょうが、気分転換には良いのではないでしょうか。

お祝い

次に思いつくのはお祝いでの活用です。

例として提供しているサービスに関連したカスタムメトリクスがあるとします。

・総アクセス数
・総ユーザー数
・リリースから経過した日数

そのカスタムメトリクスを元にCloud Watch Alarmを設定し、ミラーボールと連携させておきます。

そうすることで、総アクセス数〇〇万回! 総ユーザー数〇〇万人! リリース〇周年! みたいな社内向けのお祝いにミラーボールを使えるというわけです。

オフィスにミラーボールを設置しておけば、それなりに盛り上がるのではないでしょうか。

どうやって実装する?

さて、この意味不明な要件をどのように実装すればいいのでしょうか。
実装箇所ごとに、具体的な実装するか考えてみましょう。

1. ミラーボールの電源を制御をする方法

今回のミラーボールはUSB端子で給電するタイプです。
なのでAC電源/アダプター経由でUSBケーブルを接続して、ミラーボールに電力を供給することになります。
なので、ACアダプターの電源供給を制御できればなんとかなりそうです。

一般的にスマートプラグという名称でAC電源を制御できるものが売っているので、
今回はその中でも有名らしい?SwitchBotを利用します。

2. Cloud Watch Alarm とスマートプラグを連動させる方法

次にCloud Watch Alarmとおうちのスマートプラグを連動させる必要があります。
具体的には以下の流れで連携します。

  • Cloud Watch Alarm → Lambda → スマートプラグ
      1. メトリクスの閾値が超過することで、 Cloud Watch Alarm がアラーム状態になる
      1. アラームと連動して、Lambda を起動
      1. Lambda から SwitchBot API で SwitchBot を制御

急にSwitchBot APIというものが出てきましたが、
これはSwitchBotの公式(OpenWonderLabs)が用意している各種製品のAPIです。
これを使うことでめちゃめちゃ手軽にプログラムからSwitchBotを制御できます。

https://github.com/OpenWonderLabs/SwitchBotAPI

元々はLambda連動先の受け口をおうち側で用意して、そこからスマートプラグを操作する予定でしたが、
より簡単なSwitchBot APIという方法があったので今回はこれを利用します。

構成図

というわけで、今回の構成はこんな感じです。
SwitchBotとミラーボール以外は一般的な構成ではないでしょうか。

mirror-ball.drawio

準備するもの

ミラーボール

これがないと始まりません、本ブログのメインパーソナリティです。
ただし今回の構成はAC電源を利用するものであれば何でも使えるので、お好みのものをご利用ください。

PXL_20250911_161835060_copy_750x1000

ちなみにこのミラーボールは Amazon で買いました。
普段の買物でよく利用してはいますが、改めてほんとなんでも売ってるなー、と関心しました。

あと本当は吊り下げタイプの THE ミラーボール 的なものが欲しかったのですが、賃貸なので自重しました。

スマートプラグ

AC電源のオンオフを制御するために必須です。
前述の通り今回私は SwitchBot を使っていますが、プログラムから制御できるものであれば何でも大丈夫だと思います。

PXL_20250911_161748207_copy_750x1000

構築

おうち側のリソース作成

1. SwitchBot アプリのインストールとトークン取得

まずはSwitchBot APIを利用するために、
SwitchBot公式のスマホアプリをインストールします。

https://play.google.com/store/apps/details?id=com.theswitchbot.switchbot&hl=ja

インストールが完了したらアプリを開いて、
[プロフィール] > [設定] > [基本データ]の順で設定画面を開きます。

Screenshot_20250810-225025_copy_432x969

画面の[アプリバージョン]という項目を連打すると、
[開発者向けオプション]という項目が現れます。

Screenshot_20250810-225137_copy_432x969

[開発者向けオプション]の開くと[トークンを取得]というボタンをクリックすると、
トークンとクライアントアクセストークンが取得できます。(画像は取得後の画面です)
このトークンは後ほどコード内で使うので、メモしておきます。

Screenshot_20250810-225146_copy_432x969

2. SwitchBot 端末登録

次はデバイスをSwitchBotのアプリへ登録します。
ホームから「デバイスを追加」をクリックします。

Screenshot_20250810-225552_copy_432x969

次に追加するデバイスに応じてします。
今回私はプラグミニを購入しているので、「プラグミニ(JP)」を選択します。

Screenshot_20250810-225603_copy_432x969

あとは表示される手順に従ってセットアップしてきます。

Screenshot_20250810-225610_copy_432x969

まずはプラグミニをコンセントに挿して電源をつけ、

PXL_20250911_162600292_copy_750x1000

アプリ側で検出されるとwifiの設定画面が出るので、パスワードを入力し、

Screenshot_20250810-225627_copy_432x969

名前などを適当に設定して、

Screenshot_20250810-225914_copy_432x969

ここまでの設定で問題がなければ無事追加できます。

Screenshot_20250810-225919_copy_432x969

ホーム画面に戻ると先ほど追加したデバイスが表示されているはずです。

Screenshot_20250810-230041_copy_432x969 1

またプラグミニのランプも白くなっているはずです。

PXL_20250911_162620006_copy_750x1000

アプリのデバイスの画面から電源のマークをクリックして、正常にオン/オフを制御できればOKです。

Screenshot_20250810-230047_copy_432x969

3. デバイスID 取得

Switch Bot APIで操作するにあたり、デバイスIDを取得する必要があります。
これは各デバイスに個別に割り当てられているIDです。

デバイスIDはアプリからも確認できますが、今回はSwitch Bot APIで取得してみます。

get-device-list.py
			
			import time
import hashlib
import hmac
import base64
import requests

token = ''
secret = ''
nonce = ''

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())
print('Authorization: {}'.format(token))
print('t: {}'.format(t))
print('sign: {}'.format(str(sign, 'utf-8')))
print('nonce: {}'.format(nonce))

headers = {
    'Authorization': token,
    't': str(t),
    'sign': str(sign, 'utf-8'),
    'nonce': nonce,
    'Content-Type': 'application/json'
}

try:
    response = requests.get(
        'https://api.switch-bot.com/v1.1/devices', headers=headers)
    print(f'Status Code: {response.status_code}')
    print(f'Response: {response.text}')
except requests.exceptions.RequestException as e:
    print(f'Request failed: {e}')

		

あとは仮想環境を立てて、実行します。

			
			python3 -m venv venv;
source venv/bin/activate && pip3 install requests;
python3 get-list.py;

		

一部マスキングしていますが、以下のようなレスポンスが帰ってきます。
この中の deviceId を後ほど利用します。

			
			Authorization: XXXXXXXXXXXXXXXXX
t: XXXXXXXXXXXXXXXXX
sign: XXXXXXXXXXXXXXXXX
nonce: 
Status Code: 200
Response: {"statusCode":100,"body":{"deviceList":[{"deviceId":"XXXXXXX","deviceName":"プラグミニ(JP) 36","deviceType":"Plug Mini (JP)","enableCloudService":true,"hubDeviceId":""}],"infraredRemoteList":[]},"message":"success"}

		

4. ミラーボール セッティング

最後にミラーボールの電源ケーブルを繋いで、プラグミニはオフにしておきます。
これでおうち側の準備は完了です。

PXL_20250911_162846501_copy_750x1000

AWS 側のリソース作成

Lambda用プログラム

Lambdaで実行するプログラムは以下です。
シークレットやデバイスIDはLambdaの環境変数に設定したものを取得して使っています。

main.py
			
			import time
import hashlib
import hmac
import base64
import requests
import json
import os

def lambda_handler(event, context):
    token = os.environ.get('SWITCHBOT_TOKEN')
    secret = os.environ.get('SWITCHBOT_SECRET')
    device_id = os.environ.get('DEVICE_ID')

    command = event.get('command', 'turnOn')
    nonce = ''

    try:
        t = int(round(time.time() * 1000))
        string_to_sign = '{}{}{}'.format(token, t, nonce)
        string_to_sign = bytes(string_to_sign, 'utf-8')
        secret_bytes = bytes(secret, 'utf-8')
        sign = base64.b64encode(
            hmac.new(secret_bytes, msg=string_to_sign, digestmod=hashlib.sha256).digest())

        headers = {
            'Authorization': token,
            't': str(t),
            'sign': str(sign, 'utf-8'),
            'nonce': nonce,
            'Content-Type': 'application/json'
        }

        payload = {
            "command": command,
            "commandType": "command"
        }

        url = f'https://api.switch-bot.com/v1.1/devices/{device_id}/commands'
        response = requests.post(url, headers=headers,
                                 data=json.dumps(payload))

        if response.status_code == 200:
            response_data = response.json()

            return {
                'statusCode': 200,
                'body': json.dumps({
                    'message': f'コマンド "{command}" が正常に送信されました',
                    'device_id': device_id,
                    'switchbot_response': response_data,
                    'success': True
                }, ensure_ascii=False)
            }
        else:
            return {
                'statusCode': response.status_code,
                'body': json.dumps({
                    'message': f'HTTPエラーが発生しました: {response.status_code}',
                    'error': response.text,
                    'success': False
                }, ensure_ascii=False)
            }

    except requests.exceptions.RequestException as e:
        return {
            'statusCode': 500,
            'body': json.dumps({
                'message': 'リクエストに失敗しました',
                'error': str(e),
                'success': False
            }, ensure_ascii=False)
        }

    except Exception as e:
        return {
            'statusCode': 500,
            'body': json.dumps({
                'message': '予期しないエラーが発生しました',
                'error': str(e),
                'success': False
            }, ensure_ascii=False)
        }


		

またrequestsライブラリはデフォルトでLambda実行環境に含まれていないので、
Lambdaレイヤー用のフォルダを用意しておきます。

			
			mkdir -p layer/python/lib/python3.13/site-packages;
python3 -m pip install requests -t layer/python/lib/python3.13/site-packages/;

		

Terraformでのリソース作成

例のごとくTerraformでAWS上のリソースを作成します。

まずは terraform.tfvars にクレデンシャル情報を設定します。
先ほど取得した値をそれぞれ当てはめてください。

terraform.tfvars
			
			switchbot_token  = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
switchbot_secret = "XXXXXXXXXXXXXXXXXXXXXXXXXX"
device_id        = "XXXXXXXXXXXXX"

		

作成しているリソースは以下の通りです。

  • Lambda関数
  • Lambda用IAMロール
  • Lambdaレイヤー (Python requestsライブラリ用)
  • Lambdaアクセス権限 (CloudWatch Alarm用)
main.tf
			
			terraform {
  required_version = ">= 1.0"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
    archive = {
      source  = "hashicorp/archive"
      version = "~> 2.0"
    }
  }
}

provider "aws" {
  region = var.aws_region
}

# Variables
variable "aws_region" {
  description = "AWS region"
  type        = string
  default     = "ap-northeast-1"
}

variable "switchbot_token" {
  description = "SwitchBot API token"
  type        = string
  sensitive   = true
}

variable "switchbot_secret" {
  description = "SwitchBot API secret"
  type        = string
  sensitive   = true
}

variable "device_id" {
  description = "SwitchBot device ID"
  type        = string
}

# Create Lambda Layer for requests dependency
data "archive_file" "requests_layer" {
  type        = "zip"
  output_path = "${path.module}/requests_layer.zip"
  source_dir  = "${path.module}/layer"
}

resource "aws_lambda_layer_version" "requests_layer" {
  filename            = data.archive_file.requests_layer.output_path
  layer_name          = "requests-layer"
  compatible_runtimes = ["python3.13"]
  source_code_hash    = data.archive_file.requests_layer.output_base64sha256

  description = "Layer containing requests library"
}

# Create Lambda function zip file
data "archive_file" "lambda_zip" {
  type        = "zip"
  output_path = "${path.module}/lambda_function.zip"
  source {
    content  = file("${path.module}/main.py")
    filename = "lambda_function.py"
  }
}

# IAM role for Lambda execution
resource "aws_iam_role" "lambda_execution_role" {
  name = "switchbot-lambda-execution-role"

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Action = "sts:AssumeRole"
        Effect = "Allow"
        Principal = {
          Service = "lambda.amazonaws.com"
        }
      }
    ]
  })
}

# Attach basic execution policy
resource "aws_iam_role_policy_attachment" "lambda_basic_execution" {
  policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
  role       = aws_iam_role.lambda_execution_role.name
}

# Lambda function
resource "aws_lambda_function" "switchbot_controller" {
  filename         = data.archive_file.lambda_zip.output_path
  function_name    = "switchbot-controller"
  role             = aws_iam_role.lambda_execution_role.arn
  handler          = "lambda_function.lambda_handler"
  runtime          = "python3.13"
  timeout          = 30
  source_code_hash = data.archive_file.lambda_zip.output_base64sha256

  layers = [aws_lambda_layer_version.requests_layer.arn]

  environment {
    variables = {
      SWITCHBOT_TOKEN  = var.switchbot_token
      SWITCHBOT_SECRET = var.switchbot_secret
      DEVICE_ID        = var.device_id
    }
  }

  depends_on = [
    aws_iam_role_policy_attachment.lambda_basic_execution,
  ]
}

# Lambda permission for CloudWatch Alarms
resource "aws_lambda_permission" "allow_cloudwatch" {
  statement_id  = "AllowExecutionFromCloudWatchAlarms"
  action        = "lambda:InvokeFunction"
  function_name = aws_lambda_function.switchbot_controller.function_name
  principal     = "lambda.alarms.cloudwatch.amazonaws.com"
}

# Outputs
output "lambda_function_name" {
  description = "Name of the Lambda function"
  value       = aws_lambda_function.switchbot_controller.function_name
}

output "lambda_function_arn" {
  description = "ARN of the Lambda function"
  value       = aws_lambda_function.switchbot_controller.arn
}

output "lambda_layer_arn" {
  description = "ARN of the requests layer"
  value       = aws_lambda_layer_version.requests_layer.arn
}

		
リソース作成
			
			terraform init;
terraform apply;

		

あとは適当なEC2インスタンスのメトリクスを指定して、Cloud Watch Alarmを設定します。
アクションの連携先は、Terraformで作成したLambda関数を指定します。

スクリーンショット 2025-09-12 1.56.01

スクリーンショット 2025-09-12 1.44.53

スクリーンショット 2025-09-12 1.45.37

ミラーボール、光ります。

では実際にアラートを発生させて光らせてみます。
EC2を停止して、その後数分待つと…。

スクリーンショット 2025-09-12 1.49.12

Screenshot_20250913-161235-min-min

光りました、綺麗だね…。

Screenshot_20250913-161321-min-min

さいごに

以上、Cloud Watch Alarmで自宅のミラーボールを光らせてみました。

実は私はIoT方面には疎く、ほぼ素人の状態で始めたのですが、
SwitchBotを利用することで想像以上に簡単に連動させることができました。

皆様も自宅で持て余しているミラーボールがあれば、ぜひ光らせてみてはいかがでしょうか。
もちろん、ミラーボールでなくても構いません。
ランプ、扇風機、コーヒーメーカー、プロジェクター…お好きな電化製品をクラウド監視と連動させて、日々の運用に少しの楽しさを加えてみてください。

最後に、このような突拍子もないアイデアでも、現代の技術があれば比較的簡単に実現できる時代になったことに、改めて技術の進歩を感じずにはいられません。
皆様も日常の「あったら面白いな」を技術で実現してみませんか?

この記事をシェアする

FacebookHatena blogX

関連記事