【Pythonでスマートホーム】SwitchBotの操作をDiscordにリアルタイム通知!Rye環境での開発奮闘記

ALL

こんにちは、Tech Samuraiです!
私の開発環境の心臓部であるMac miniは、24時間稼働し続ける頼れるサーバーでもあります。今回は、このMac miniとモダンなPython環境Ryeを活用して、身の回りのスマートホームデバイス「SwitchBot」の操作履歴を、**Discordにリアルタイムで自動通知する**スクリプトを構築した際の、開発の全記録をお届けします。

この仕組みがあれば、「家の鍵が閉まった/開いた」「ハブ経由で家電が作動した」といった重要なイベントを、作業中のPCや外出先のスマホで即座に確認できます。しかし、その完成までには、Pythonのモジュールシステムという、一見シンプルながら奥深い「壁」が立ちはだかりました。その奮闘の記録も併せてご覧ください。


1. 動作の仕組みと準備

このスクリプトは、以下のコンポーネントで構成されます。

  • SwitchBot Cloud API: デバイスの状態を取得するための公式インターフェース。
  • Discord Webhook: PythonスクリプトからDiscordの特定チャンネルにメッセージを送信するための、専用のURL。
  • Python (Rye環境): APIとの通信やメッセージ送信を担う、私たちの司令塔です。

🚨 重要な認証情報の準備

このスクリプトを動かすには、以下の3つの認証情報が必要です。これらをプロジェクトのルートに作成した.envというファイルに記述し、安全に管理します。(ライブラリとしてpython-dotenvの利用を推奨します)

# .env ファイルの中身
SWITCHBOT_TOKEN="あなたのトークン"
SWITCHBOT_SECRET="あなたのシークレットキー"
DISCORD_WEBHOOK_URL="あなたのWebhook URL"

2. コード実装 Part 1:汎用的なDiscord送信モジュール

まず、どんなプロジェクトでも再利用できるように、Discordへメッセージを送信する機能だけを独立したファイルに切り出します。

ファイル名: `discord_sender.py`

import requests
import json

def send_discord_webhook(message: str, url: str) -> bool:
    """Discord Webhookを介してメッセージを送信する関数"""
    if not message or not url:
        return False

    headers = {"Content-Type": "application/json"}
    payload = {"content": message}

    try:
        response = requests.post(url, headers=headers, data=json.dumps(payload))
        response.raise_for_status() # エラーがあれば例外を発生させる

        print("✅ Discordへの通知に成功しました。")
        return True

    except requests.exceptions.RequestException as err:
        print(f"❌ Discord送信エラーが発生しました: {err}")
        return False

3. 開発の壁:`ModuleNotFoundError`との遭遇

コードを整理するため、作成したスクリプトを`py_script`というサブディレクトリに格納しました。

my_project/
├── .env
├── py_script/
│   ├── switchbot_monitor.py  (メインスクリプト)
│   └── discord_sender.py     (送信モジュール)
└── pyproject.toml

そして、メインスクリプト`switchbot_monitor.py`から、送信モジュールをfrom discord_sender import send_discord_webhookのようにインポートして実行したところ、無情なエラーが…。

ModuleNotFoundError: No module named 'discord_sender'

同じフォルダにあるはずなのに、なぜ見つけられないのか? これが、Pythonのモジュールシステムの罠でした。


4. 壁の突破:パッケージと相対インポートの理解

原因:
Pythonは、デフォルトではサブディレクトリを「パッケージ」として認識しません。ただのフォルダとしてしか見ていないため、その中にある他のファイル(モジュール)を見つけることができないのです。

解決策:

  1. パッケージとして認識させる: `py_script`ディレクトリ内に、中身が空の**`__init__.py`**というファイルを作成します。このファイルが存在することで、Pythonに「このフォルダは、モジュールをまとめた一つのパッケージですよ」と教えることができます。
  2. インポートパスの修正: `switchbot_monitor.py`内のインポート文を、**相対インポート**に変更します。 # 変更前: from discord_sender import ... # 変更後: from .discord_sender import ... 先頭にドット.`を付けることで、「同じパッケージ(ディレクトリ)内にあるdiscord_senderモジュールから」という意味になり、正しくファイルを見つけられるようになります。

5. 完成版メインスクリプトと実行

上記の修正を加え、SwitchBot APIとの通信ロジックを実装した、最終的なメインスクリプトがこちらです。

ファイル名: `py_script/switchbot_monitor.py`

# 🚨 修正ポイント: 相対インポート
from .discord_sender import send_discord_webhook

import os
import time
import hashlib
import hmac
import base64
import json
import requests
from datetime import datetime
from dotenv import load_dotenv

# 実行場所に関わらずプロジェクトルートの.envを読み込む
load_dotenv(dotenv_path=os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), '.env'))

# --- 環境変数の読み込み ---
TOKEN = os.getenv("SWITCHBOT_TOKEN")
SECRET = os.getenv("SWITCHBOT_SECRET")
WEBHOOK_URL = os.getenv("DISCORD_WEBHOOK_URL") 
# ... (API署名生成やデバイス状態取得の関数は省略) ...

def main():
    if not all([TOKEN, SECRET, WEBHOOK_URL]):
        print("エラー: APIキーまたはWebhook URLが設定されていません。`.env`ファイルを確認してください。")
        return

    # (ここにAPIヘッダー生成、デバイスリスト取得などのロジックが入る)

    # Lockイベントが発生したと仮定した通知ロジック
    event_message = f"🔑 ロックデバイス XXX がアンロックされました! ({datetime.now().strftime('%H:%M:%S')})"

    # 外部関数に引数を渡して実行
    send_discord_webhook(event_message, WEBHOOK_URL)

if __name__ == "__main__":
    main()

最終的な実行コマンド

この構造になったら、プロジェクトのルートディレクトリ(my_project/)で、以下のコマンドを実行します。

rye run python -m py_script.switchbot_monitor

-m`オプションを付けてパッケージとして実行することで、Pythonが正しくインポートパスを解決してくれます。


まとめ

今回は、SwitchBotとDiscordという2つのサービスをPythonで連携させ、スマートホームの通知システムを構築しました。そして、その過程で遭遇した`ModuleNotFoundError`を通して、Pythonの**パッケージ**や**相対インポート**といった、コードを整理・構造化する上で非常に重要な概念を学びました。

エラーは、単なる障害ではありません。それは、私たちがシステムの仕組みをより深く理解するための、最高の道標なのです。

コメント

タイトルとURLをコピーしました