こんにちは、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は、デフォルトではサブディレクトリを「パッケージ」として認識しません。ただのフォルダとしてしか見ていないため、その中にある他のファイル(モジュール)を見つけることができないのです。
解決策:
- パッケージとして認識させる: `py_script`ディレクトリ内に、中身が空の**`__init__.py`**というファイルを作成します。このファイルが存在することで、Pythonに「このフォルダは、モジュールをまとめた一つのパッケージですよ」と教えることができます。
- インポートパスの修正: `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の**パッケージ**や**相対インポート**といった、コードを整理・構造化する上で非常に重要な概念を学びました。
エラーは、単なる障害ではありません。それは、私たちがシステムの仕組みをより深く理解するための、最高の道標なのです。
コメント