1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

VLM × Raspberry Pi × SwitchBotでカーテンを自動化してみる。

1
Posted at

はじめに

「VLM × Raspberry Pi × SwitchBot」で、外に干された洗濯物が検出されなければ、自動化されたカーテンを閉める」という仕組みを作りました。この記事では、その記録を残します。

少々変わった取り組みではございますが、趣味の一環として試してみたことですので、お時間がございましたら、ご覧いただければいいなと思っています🙇。

詳細なソースコードやラズパイのセットアップ、各APIの使い方、プログラミングの基本等は書いていないため、ご注意ください。

きっかけ

SwitchBotカーテン3を買ったことで、カーテンは自動で開け閉めされるようになりました。しかし、ここで問題となったのは 「洗濯物を取り入れるのを忘れてしまう」 ことです。カーテンがスケジュールで、夕方に閉められると、洗濯物が見えなくなるため、完全に忘れられ、風呂や着替えのタイミングで気づくことが多くなりました。

他により良いアプローチはありますが(洗濯した日は閉めない予約をするとか...)、ラズパイとVLMの推論を試したかったこともあり、これらを利用して完全に自動化した仕組みを作りました。

理由

Q: VLMを使う理由は?
A: 手軽に実装できるから
洗濯物を検出するCNN等のAIモデルの方が、VLMよりも軽量で高速ではありますが、構築自体はVLMの方が簡単に実装できます。

Q: クラウドではなくローカルで推論する理由は?
A: 金銭的コストの削減、プライバシー保護のため。API料金をかけず、学習されるリスクをなくすためです。Gemini APIは無料で使用できますが、学習データに使用されることが明記されています。毎日外に干された洗濯物(家周囲の背景も)を送ることになるので…。

Q: VLMの推論をラズパイで行う理由は?
A: 単にローカルのVLMの推論を試したり、実用的に使いたかったことや、リアルタイム性もそこまで追求していないことが理由です。このシステムは、早いレスポンスは求めていません。

実際に使用したもの

Raspberry Pi 5 (16GB)

ロジクール Webカメラ C270n HD 720P

NIMASO USB 延長ケーブル

SwitchBotカーテン3

Raspberry Pi 5のメモリは使用するAIモデルによりますが、8GBでも十分かと思われます。
私のラズパイは他のプログラム(自作のNAS等)も実行しているので、余裕を持って16GBにしています。

SwitchBotカーテンも2、3どちらでも良いです。

※ 明記していませんが、Raspberry Piの周辺機器も以下のように揃えています。

  • 電源アダプター(27W)
  • 冷却ファン(かなり大事、とても発熱するため必須)
  • MicroSDカード(128GB)
  • LANケーブル(Wi-Fiが搭載されているので必需品ではない)

構成

ソフトウェアの構成

ソフトウェア構成.png

cronコマンドで定時(16:00)実行
cronで毎日16:00に実行します。

この後(今日)の日の入り時間を計算
日の入り時間の計算は、astralモジュールを使用しました。ローカルで完結し、太陽との角度で決めるため、年中暗くなるタイミングでカーテンを閉められます(曇りの日は暗くなるのが早いため、照度センサーがあればそれがベストです)。

Webカメラで外を撮影
何枚か撮影し、よかったものを採用する。また、カメラが安定させるために、起動後少し待ってから撮影する。そのほか色調や明るさを調整する。

今日のatコマンドを指定する
atコマンドは、指定した時間に一回だけプログラムを実行できるLinuxのコマンドです。cronコマンドは同じ時間に定期で実行でき、再度指定する必要はありませんが、日没は一年中同じではないためatコマンドで毎日指定します。

atコマンドで実行される
先ほど設定されたatコマンドで実行されます。

画像をVLMに渡して解析
ollamaモジュールでVLMの推論を行います。画像と趣旨を入力します。今回はminicpm-vというモデルを使用しました。

SwitchBot APIでカーテンを閉める
SwitchBot APIでカーテン3に閉めるよう指示します。

本当は、プログラム一つで完結し、cronは使わない予定でした。しかし、私のラズパイは定時で電源をON/OFFしており、atコマンドがどうやら電源を切ってしまうと挙動がおかしくなってしまうため、プログラムを上記のように2つに分けました(atコマンドを再インストールする羽目に…)。つまり、atコマンドがラズパイ起動中で完結する仕組みにしました。原因がわからなすぎて、最後の最後で一番時間を費やしました...

ハードウェアの構成

ハードウェア構成.png

外が見える位置にwebカメラを貼り付けました。カーテンの内側(カーテンと窓の間)かつ窓の可動に耐えられ、綺麗に映る場所… 意外と苦労しました。結果的に、窓のフレームにWebカメラをテープで貼り付けています。またスライドされる窓に設置したため、3mの延長コードを使用し、窓のスライドに耐えられるようにしました。

コード

以下が、cronで定時実行されるreservation.pyの一部です。

reservation.py
# atコマンドでスケジューリング
cmd = f"echo '/path/to/venv/python /path/to/close_curtain.py' | at {at_time_str}"
print(f"CMD: {cmd}")
result = os.system(cmd)

Pythonでatコマンドを指定します。
at_time_strはカーテンのアクションをする時間です。astral, pytz, datetimeモジュール等を使用して事前に求めます。

cronで定時実行を設定するには

crontab -e

で、モーダルダイアログを開いた後、
16:05に実行したいなら

5 16 * * * /path/to/venv/python /path/to/reservation.py

を入れて保存します。

以下が、実行予定となるclose_curtain.pyの簡略化したコードです。

close_curtain.py
cap = cv2.VideoCapture(0) # 0はどのカメラかで指定する

# カメラ安定化用のウォームアップ
for _ in range(20):
    cap.read()
    time.sleep(0.05)
ret, frame = cap.read()

store_path = f"path/to/folder/{int(time.time())}.jpg" # 画像のファイル名をUNIXタイムにする
cv2.imwrite(store_path, frame) # 記録のために保存する。

response = ollama.generate(model="moondream:v2", prompt="Output {'laundry':True} if there is laundry(towels, pants, clothes) in the balcony's image. Otherwise, output {'laundry':False} if there are no laundry in the balcony's image.", format="json", images=[store_path])

try:
    response_dict = json.loads(response['response'])
except:
    # VLMが生成ミス、json構造が崩れている等
    return

if response_dict["laundry"]:
    # カーテン閉めず終了。
    return
else:
    # カーテン閉める。SwitchBot APIでカーテンを閉める処理。
    headers = {}
    nonce = uuid.uuid4()
    now_time = int(round(time.time() * 1000))
    string_to_sign = f"{YOUR_SWITCHBOT_API_KEY}{now_time}{nonce}".encode('utf-8')
    sign = base64.b64encode(hmac.new(YOUR_SWITCHBOT_SECRET_KEY.encode('utf-8'), msg=string_to_sign, digestmod=hashlib.sha256).digest())
    headers['Authorization'] = YOUR_SWITCHBOT_API_KEY
    headers['Content-Type'] = 'application/json'
    headers['charset'] = 'utf8'
    headers['t'] = str(now_time)
    headers['sign'] = sign.decode('utf-8')
    headers['nonce'] = str(nonce)
    params = {"command": "turnOff", "parameter": "default", "commandType": "command"}
    response = requests.post("https://api.switch-bot.com/v1.0/devices/DEVICE_ID/commands", json=params, headers=headers)

YOUR_SWITCHBOT_API_KEYとYOUR_SWITCHBOT_SECRET_KEYは、
SwitchBotアプリから取得し、置き換えます。

DEVICE_IDは

response = requests.get("https://api.switch-bot.com/v1.1/devices", headers=headers)

でデバイス一覧を取得し、操作対象のカーテンのIDを見つけて、置き換えます。

結果

様々なVLMを試しましたが、minicpm-vというモデルで一番精度が高そうでした。atコマンドが増殖しないか数日間チェックしていこうと思います。

問題点

  1. 画像の質:光が当たりすぎて外を映すカメラが白飛びすると終わり。例えば西日。また、カメラを遮るものがあるともちろん見えないので、VLMの推論が意味なくなる
  2. ファンがうるさい:1~2分程度ファンが高速で回転するため、ちょっとその間はうるさい
  3. 誤判定する可能性:洗濯物がないのに、あると判断してしまうことがあるかもしれない

感想

かなりマニアックな目的でしたが、理想的な仕組みが作れました。Raspberry PiでVLMを動かし、実用的な仕組みが作れてよかったです!
次はastralモジュールやcron等を使わずに、照度センサーでもやりたいと思います!

ここまでご覧いただき、ありがとうございました!
不備などありましたらご連絡ください!

参考

SwitchBot API - GitHub
ollama-python - GitHub

Whimsical フローチャート作成に使用

ChatGPT この記事は、ChatGPTと共同で作成しました。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?