CopyButton

2026年5月1日金曜日

スマホのリモート操作で iOS アプリをビルド&TestFlight 配信する【SSH + build keychain・2026年版】

この記事は「外出先のスマホからでも自宅 Mac の Xcode を叩いて iOS アプリを TestFlight に上げたい」エンジニア向け。スマホ → 母艦ノートPC(WSL2)→ 別マシンの Mac に多段 ssh で繋ぎ、archive → IPA → TestFlight アップロードまで 1 コマンドで完結させる手順。codesign が SSH で詰まる理由と回避策(専用 build keychain)、配信スクリプト 3 本の中身も全部書く。

ノートPCのターミナル・Mac mini・スマホが連携してアプリを配信している作業デスクの写真

この記事でできること

  • 外出先のスマホから(または WSL2 のターミナルから)SSH 1 行で TestFlight に新ビルドが上がる
  • Mac は Xcode と xcodebuild の実行環境としてだけ使い、普段は触らない
  • App Store Connect API Key.p8)認証で 2 段階認証ダイアログを出さない
  • VPS や GitHub Actions を使わずに自宅 LAN 内で完結(Mac mini や旧 MacBook の余生活用にも向く)

4つのアプローチ比較

構成 SSH + 自前スクリプト
(本記事)
Mac で Xcode 操作 GitHub Actions (macos) fastlane 主導
月額コスト 0 円(自宅 Mac の電気代のみ) 0 円 macOS runner は分単価高め 0 円(自宅 Mac)
エディタ WSL2 の好みのもの(VS Code / vim 等) Xcode 任意 任意
配信 1 回の所要時間 3〜6 分(archive + IPA + altool) 5〜10 分(GUI 経由) push 後 10〜15 分(CI 起動含む) 3〜6 分
秘匿情報の置き場 Mac ローカル(~/.appstoreconnect Mac ローカル GitHub Secrets Mac ローカル / Match

構成図

WSL2(コード編集マシン)
エディタ + git push + ssh の起点
▼ git push
▼ ssh + bash scripts/upload.sh
Mac(Xcode 専用 archive サーバ)
build.keychain-db / xcodebuild / altool
▼ xcrun altool --upload-app
App Store Connect / TestFlight

使ったもの

なぜ setup_build_keychain.sh が必要か

普通に Mac の Terminal で xcodebuild archive すれば通るのに、SSH 経由だと codesign が「キーチェーンを開けません」で落ちるのが本記事のスタートライン。

原因: login keychain は SSH ログインで unlock しても codesign から触れない。GUI ログインセッションに紐づいた security agent が居ないため、ACL 上の「アプリ確認なしに使ってよい」リストが働かず、対話プロンプトを出そうとして失敗する。

解決策は 専用の build keychain を作って Apple Distribution 証明書を複製、ACL を緩めて codesign から GUI 確認なしに使える状態にする。これを scripts/setup_build_keychain.sh として 1 度だけ Mac の Terminal(GUI セッション)で実行する。

以下のコード中の placeholder は自分の環境の値に置き換えてください({kcpw} は build keychain をローカルでアンロックするための任意文字列で、外部秘匿情報ではありません)。

{user}                        自分のユーザ名(Mac の login user)
{mac_addr}                    Mac の IP/ホスト名(例: 192.168.x.y)
{app_name}                    アプリ名(Xcode scheme / archive 名と一致)
{bundle_id}                   Bundle ID(例: com.example.MyApp)
{team_id}                     Apple Developer Team ID(10 文字)
{kcpw}                        build keychain ローカル鍵(任意の文字列)
{provisioning_profile_name}   Provisioning Profile 名
#!/bin/bash
set -euo pipefail

KCNAME="build.keychain"
KCPATH="$HOME/Library/Keychains/${KCNAME}-db"
KCPW="{kcpw}"                  # build keychain アンロック用(ローカル container 鍵。任意の文字列)
EXPORTPW="$(uuidgen)"          # .p12 一時暗号用(本スクリプト中だけ使用)

if [[ -f "$KCPATH" ]]; then
    echo "build keychain は既に存在: $KCPATH"; exit 1
fi

# 1. build keychain 作成
security create-keychain -p "$KCPW" "$KCNAME"
security set-keychain-settings -lut 21600 "$KCNAME"
security unlock-keychain -p "$KCPW" "$KCNAME"

# 2. login keychain から identity を export(GUI で「常に許可」を選ぶ)
TMPP12="$(mktemp -t build-cert).p12"
trap 'rm -f "$TMPP12"' EXIT
security export -k "$HOME/Library/Keychains/login.keychain-db" \
    -t identities -f pkcs12 -P "$EXPORTPW" -o "$TMPP12"

# 3. build keychain へ import + codesign / productbuild が触れるよう -T 指定
security import "$TMPP12" -k "$KCNAME" -P "$EXPORTPW" \
    -T /usr/bin/codesign -T /usr/bin/security \
    -T /usr/bin/productbuild -T /usr/bin/productsign

# 4. ACL で GUI プロンプトを抑制
security set-key-partition-list \
    -S 'apple-tool:,apple:,codesign:' -s -k "$KCPW" "$KCNAME"

# 5. search list の先頭に追加
ORIGINAL_LIST="$(security list-keychains -d user | sed -e 's/^[[:space:]]*//' -e 's/"//g' | tr '\n' ' ')"
security list-keychains -d user -s "$KCNAME" $ORIGINAL_LIST

security find-identity -v -p codesigning "$KCNAME"

ポイントは 4 番目の set-key-partition-list。ここで apple-tool: / apple: / codesign: をアクセス許可リストに入れて、codesign 系ツールが GUI 確認なしに鍵を使える状態にする。これが SSH 経由でも証明書アクセスできる正体。

archive.sh — Release archive を非対話で生成

2 本目のスクリプト。SSH 経由で xcodebuild archive を叩く。実行前に build keychain を unlock して、6 時間自動ロックを設定する。

#!/bin/bash
set -euo pipefail

APP_NAME="{app_name}"          # ← 自分のアプリ名に置き換え
KCPW="{kcpw}"                  # ← setup_build_keychain.sh で使ったのと同じローカル鍵

ARCHIVE_PATH="${ARCHIVE_PATH:-/tmp/${APP_NAME}.xcarchive}"
BUILD_KCPW="${BUILD_KCPW:-${KCPW}}"
WORKSPACE="${WORKSPACE:-$(pwd)/${APP_NAME}.xcworkspace}"

if [[ ! -f "$HOME/Library/Keychains/build.keychain-db" ]]; then
    echo "✗ build.keychain-db が無い。setup_build_keychain.sh を先に。" >&2
    exit 1
fi

# build keychain unlock(6h 自動ロック)
security unlock-keychain -p "$BUILD_KCPW" build.keychain
security set-keychain-settings -lut 21600 build.keychain

# archive
rm -rf "$ARCHIVE_PATH"
LOG=/tmp/archive.log
xcodebuild \
    -workspace "$WORKSPACE" \
    -scheme "$APP_NAME" \
    -configuration Release \
    -destination 'generic/platform=iOS' \
    -archivePath "$ARCHIVE_PATH" \
    archive \
    > "$LOG" 2>&1 || true

if grep -q "ARCHIVE SUCCEEDED" "$LOG"; then
    echo "✓ ARCHIVE SUCCEEDED -> $ARCHIVE_PATH"
else
    echo "✗ ARCHIVE FAILED (log: $LOG)"
    grep -B1 -A4 -E 'error:|errSec|FAILED' "$LOG" | tail -30
    exit 1
fi

ログを /tmp/archive.log に逃がして、成否を ARCHIVE SUCCEEDED 文字列で判定する形。xcodebuild の標準出力は冗長なので SSH 越しに見るのは消耗する。

ExportOptions.plist — Manual signing を明示

archive から IPA をエクスポートするときの設定ファイル。Auto signing で何度かハマったので、Manual signing で profile 名と Team ID を明示する形に固定した。git にコミットしても秘匿情報は含まれない。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
  "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>method</key>
    <string>app-store-connect</string>
    <key>teamID</key>
    <string>{team_id}</string>
    <key>signingStyle</key>
    <string>manual</string>
    <key>provisioningProfiles</key>
    <dict>
        <key>{bundle_id}</key>
        <string>{provisioning_profile_name}</string>
    </dict>
    <key>uploadSymbols</key><true/>
    <key>compileBitcode</key><false/>
    <key>stripSwiftSymbols</key><true/>
</dict>
</plist>

Bundle ID とプロファイル名は自分の値に置き換える。プロファイル名は Apple Developer ポータルで作成したもの。

upload.sh — IPA エクスポート + altool アップロード

3 本目。archive を再利用して IPA を吐き、xcrun altool で TestFlight に投げる。altoolApp Store Connect API Key 認証だと 2 段階認証ダイアログが出ない。

#!/bin/bash
set -euo pipefail

APP_NAME="{app_name}"          # ← 自分のアプリ名に置き換え
KCPW="{kcpw}"                  # ← setup_build_keychain.sh で使ったのと同じローカル鍵

SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
ARCHIVE_PATH="${ARCHIVE_PATH:-/tmp/${APP_NAME}.xcarchive}"
EXPORT_DIR="${EXPORT_DIR:-/tmp/${APP_NAME}-export}"
EXPORT_OPTIONS="${EXPORT_OPTIONS:-$SCRIPT_DIR/ExportOptions.plist}"
ALTOOL_ENV="${ALTOOL_ENV:-$PROJECT_ROOT/Config/altool.env}"
BUILD_KCPW="${BUILD_KCPW:-${KCPW}}"

# Config/altool.env から ASC_KEY_ID / ASC_ISSUER_ID をロード
source "$ALTOOL_ENV"

# archive が無ければ自動で archive.sh を呼ぶ(再利用が効く)
if [[ ! -d "$ARCHIVE_PATH" ]]; then
    bash "$SCRIPT_DIR/archive.sh"
fi

# IPA 出力(exportArchive も再署名するので keychain unlock)
security unlock-keychain -p "$BUILD_KCPW" build.keychain

rm -rf "$EXPORT_DIR"
xcodebuild -exportArchive \
    -archivePath "$ARCHIVE_PATH" \
    -exportPath "$EXPORT_DIR" \
    -exportOptionsPlist "$EXPORT_OPTIONS" \
    > /tmp/export.log 2>&1 || true

IPA_PATH="$(find "$EXPORT_DIR" -maxdepth 1 -name '*.ipa' | head -n1)"
[[ -f "$IPA_PATH" ]] || { echo "✗ IPA 生成失敗"; exit 1; }

# TestFlight アップロード
xcrun altool --upload-app -f "$IPA_PATH" -t ios \
    --apiKey "$ASC_KEY_ID" --apiIssuer "$ASC_ISSUER_ID"

Config/altool.env.gitignore 済みで、中身は 2 行だけ。

ASC_KEY_ID=ABC123XYZ4
ASC_ISSUER_ID=12345678-90ab-cdef-1234-567890abcdef

.p8 ファイル本体は ~/.appstoreconnect/private_keys/AuthKey_<KEY_ID>.p8 に置く(altool がこのパスを自動で見に行く)。

WSL2 から 1 行で TestFlight に上げる

ここまで揃えると、WSL2 側の運用は 1 行で済む。

# CFBundleVersion を bump して push したあとに
ssh {user}@{mac_addr} 'cd ~/path/to/{app_name} && bash scripts/upload.sh'

3〜6 分後に TestFlight に新ビルドが現れる。App Store Connect の Processing が完了したらすぐ自分の iPhone で Install 可能。

詰まりポイント

  • codesign が GUI プロンプトを出して固まる: build keychain の set-key-partition-list を忘れている。apple-tool:,apple:,codesign: を必ず指定
  • Auto signing が別チームの Development cert にフォールバック: Release は Manual signing 固定 + Team ID 明示が安全。ExportOptions.plistsigningStyle = manual
  • build keychain が 6 時間で自動ロックされて翌日のビルドが落ちる: archive.sh / upload.sh の冒頭で毎回 security unlock-keychain を呼ぶ。一度の set-keychain-settings -lut 21600 だけだと足りない
  • SPM の dynamic framework が Embed されない: RealmSwift のような source-distributed dynamic framework は project.pbxproj の Embed Frameworks に手動登録が必要。Firebase / GoogleMobileAds の XCFramework binary target は auto-embed されるので忘れがち
  • altool の 2 段階認証ダイアログ: パスワード認証だと 2FA でブロックされる。必ず App Store Connect API Key(.p8)認証を使う
  • Mac の sshd が起動していない: システム設定 → 一般 → 共有 → リモートログイン を有効化

よくある質問

Q. Mac は普段スリープでも大丈夫?

A. 配信するときだけ叩くなら Wake-on-LAN + システム設定の「ネットワークアクセスによりスリープ解除」を ON にすれば SSH パケットで起きる。常時稼働させるなら蓋を閉じても寝ない設定(caffeinate + 電源接続中はスリープしない)にしておく。

Q. Apple Distribution の証明書はどう発行する?

A. Mac の Xcode で Settings → Accounts → Apple Developer の Manage Certificates から「Apple Distribution」を発行 → login keychain に登録される。setup_build_keychain.sh を走らせるとそれが build keychain に複製される。

Q. Provisioning Profile を更新したらどうする?

A. Apple Developer ポータルでダウンロード → Mac で開く → Xcode の Signing & Capabilities で読み込まれているか確認。ExportOptions.plist の profile 名は変わらないので、スクリプト側の修正は不要。

Q. Mac mini を専用 archive サーバにしたい

A. M2 / M4 の Mac mini なら Xcode 26 + SPM の archive で 3〜5 分。常時電源 ON でも消費電力 10W 前後。Mac mini + LINE Notify でビルド完了通知を飛ばせば、外出中でも完走を把握できる。

Q. fastlane を使ったほうが早い?

A. fastlane は match による証明書同期や lane の構造化が便利。本記事のスクリプトは 「Mac 1 台で完結する個人配信」に絞っているので fastlane の機能のごく一部だけを薄く再実装した形。チーム開発になったら fastlane に乗り換える価値はある。

※本記事の手順は執筆時点(2026 年 5 月)の Xcode 26 / macOS 15 系で動作確認しています。Apple Distribution 証明書の発行手続きや App Store Connect の UI は変わる場合があります。動かない場合は コメント欄でお知らせください。

まとめ

Xcode を WSL2 から完全リモートで動かすのは build keychain と App Store Connect API Keyの 2 点を押さえれば素直に組める。シェルスクリプト 3 本(setup_build_keychain.sh / archive.sh / upload.sh)と ExportOptions.plist 1 枚で完結する。Mac は Xcode 専用の archive マシンとして奥に置いて、普段の開発は Linux の好みの環境で行える。

この記事が役に立ったら X(Twitter)でシェアしてもらえると喜びます。

関連記事

参考

※この記事は Claude Code を使った自動更新を試しています。

ブログ用 AI 画像をローカル GPU で生成する【SDXL + IP-Adapter + img2img・実生成画像で進化を全部書く】

毎週Claude Codeに投稿してもらうブログ記事のカバー画像と部品紹介イラストを、ローカル GPU で自動生成する仕組みを作った。SDXL を素直に動かす段階から、IP-Adapter で実機写真に寄せる段階、img2img で手書き風イラストにする段階まで、生成画像が実際にどう変わるかを 6 枚の比較画像で全部書く。VRAM 6GB マシンで詰まったメモリ制約と、それを回避するために選んだアプローチも含む。

本記事の手順は全てClaude Codeに構築してもらい、記事の執筆も99% Claude Codeだが、試行錯誤の経緯は実話。

img2img + 高 denoise で生成した XIAO ESP32-S3 の水彩イラスト。本記事のゴール

この記事でできること

  • GTX 1660 SUPER(VRAM 6GB)+ メインメモリ 16GB の Linux PC で SDXL を回す全体像がわかる
  • IP-Adapter で「実機の見た目に近い」AI 画像を作れる
  • img2img + 高 denoise で「手書き風水彩イラスト」を作れる
  • VRAM 6GB マシンで起こりがちなメモリ不足の回避策がわかる

使ったもの

なぜローカル生成にしたのか

毎週ブログ記事を 1 本書くペースだと、API 課金型の画像生成サービスは「試行錯誤の心理的コスト」が積み上がる。プロンプトを 5 回練り直すたびに少額が引かれていく感覚が、実験的な調整の手を止めてしまう。ローカル GPU で完結させれば、コストは電気代だけ。気が済むまで --seed を変えて回せる。

もうひとつの理由は依存関係を自分の管理下に置きたいこと。API は仕様変更や値上げ、サービス停止で動かなくなる。ローカル環境ならモデルファイルとコードを保存しておけば、5 年後でも同じ手順で動かせる。

Step 1: ComfyUI + SDXL を素のまま動かす

まず ComfyUI を 公式サイト の手順通りにインストールし、Juggernaut XL v9 を checkpoints/ に配置する。プロンプトに「watercolor illustration of XIAO ESP32-S3」と書いて txt2img を回した結果がこれ:

参照画像なしの SDXL 出力。XIAO ESP32-S3 を指定したのに、実物とは似ていない汎用ガジェット風の絵が出る

水彩のスタイル指定は通っているが、絵は架空のガジェット。SDXL のベースモデルは「具体的なボード名」までは学習していないため、それっぽい何かを出力してくる。

Step 2: 「実機の見た目」を出すには参照画像が要る

ブログで「この XIAO ESP32-S3 で温湿度モニタを作りました」と書きたいときに、絵が違うボードでは記事の信頼性が落ちる。AI が学習していない具体的なハードを正確に出すには、実物写真を参照画像として渡す仕組みが要る。それが IP-Adapter。

Step 3: IP-Adapter Plus を導入する

カスタムノード ComfyUI_IPAdapter_pluscustom_nodes/ に clone し、Hugging Face から:

  • ip-adapter-plus_sdxl_vit-h.safetensors(~700MB)→ models/ipadapter/
  • CLIP-ViT-H-14-laion2B-s32B-b79K.safetensors(~2.5GB)→ models/clip_vision/

を DL する。XIAO の公式商品写真を参照画像として渡し、weight 0.5 で生成すると:

IP-Adapter weight 0.5 で生成した XIAO ESP32-S3。実物の構造が忠実に反映されている

実物の構造が忠実に再現される。USB-C コネクタ、シールドされた無線モジュール、QR コード状のチップマーキングまで「これは確かに XIAO ESP32-S3」と認識できる。が、よく見るとプロンプトに書いた watercolor illustration の指定は完全に消えて写実調になっている。

IP-Adapter Plus は「style transfer 型」で、参照写真の色・質感・スタイルが結果に強く転写される。これはカバー画像(実物に近づけたい)には強力だが、イラスト調にしたいときは邪魔になる

Step 4: 「写真」と「イラスト」を使い分けたい

カバー画像は写実調で OK。だが本文中の「使ったもの」セクションで部品を紹介するときは、イラスト調にして視覚的なリズムを変えたい。同じ記事内で全部写実調だと単調になる。

Step 5: IP-Adapter weight を下げてみる(失敗)

素直な発想として、IP-Adapter の weight を下げれば写真感が薄まりプロンプトの指示が通るのでは、と試す。weight を 0.3 まで下げ、ネガティブプロンプトで (photorealistic:1.6) を強く指定した結果:

IP-Adapter weight 0.3 でも参照写真の style が支配的で写実調のまま

weight 0.3 でも写真スタイルが支配的。Plus 変種は 低 weight でも style が残る性質があるため、イラスト化用途には根本的に向いていないことがわかる。

Step 6: ControlNet を試したが封印

次に試したのが ControlNet (Canny)。参照写真から輪郭線だけを抽出し、色や質感はプロンプトに完全に任せる、というアプローチ。理屈の上ではイラスト化にぴったり。

動作はする。ただし VRAM 6GB + メインメモリ 16GB の構成で、SDXL + IP-Adapter + ControlNet を同時にロードすると長時間稼働後に他プロセスとのメモリ競合で詰まる事象が頻発した。短時間だけ動けば成功するが、稼働を続けるとプロセスが応答しなくなる。

ブログ更新は cron で毎週走らせたい。その間に詰まると記事が出ない。「動作はするが信頼性が足りない」機能は cron 運用には載せられないので、保守的に封印した。

Step 7: img2img + 高 denoise で解決

残された道は img2img + 高 denoise。仕組みはこうだ:

  1. 参照写真を VAE エンコードして初期 latent に変換する
  2. KSampler の denoise を高めに設定する(0.9 推奨)
  3. KSampler は初期 latent からサンプリング過程を進めるが、denoise が高いほど「初期 latent の影響が薄れ、プロンプトの指示が支配的になる」

結果、構造(部品の輪郭・配置)はうっすら残るが、色・質感・スタイルはプロンプトが完全制御する。重要なのは:

  • カスタムノード追加なし(ComfyUI の標準ノードだけで構成できる)
  • 追加モデル DL なし
  • メモリ消費は txt2img と同等

つまり ControlNet を回避しつつ「輪郭参照 + プロンプト主導のスタイル」を実現できる。VRAM 6GB マシンで安定して回せる。

Step 8: denoise の最適値を実測で探る

img2img の denoise をいくらにするかが品質を左右する。同じプロンプト・同じ seed・同じ参照画像で 0.8 / 0.85 / 0.9 を比較した。

denoise 0.8

img2img denoise 0.8。まだ参照写真の構造が支配的で写実調のまま

まだ写真寄り。「水彩」のスタイル指定はうっすら効いているが、見た目は写実画像に水彩フィルタを軽くかけた程度。

denoise 0.85

img2img denoise 0.85。0.8 とほぼ変わらず写実寄り

0.05 上げても変化はわずか。やはり初期 latent の写真感が残る。

denoise 0.9

img2img denoise 0.9。水彩イラストと部品識別可能のバランスに到達した最終形

来た。水彩の柔らかさと、XIAO ESP32-S3 の特徴的な形(USB-C コネクタの位置、無線モジュールのシールド、castellated pin の並び)が両立している。denoise 0.9 が現状ベスト値

0.95 まで上げると今度は構造が崩れて「水彩なんだけど何のボードか分からない」絵になりやすい。0.9 が「形のヒントは残し、スタイルはプロンプト主導」のバランス点だった。

Step 9: 完成したパイプラインを実際の記事にも適用

このパイプラインで、別記事用に Raspberry Pi Pico 2 W のイラストも作ってみた。参照画像は Raspberry Pi 公式 のプレス画像、denoise 0.9、生成時間は約 2 分:

Raspberry Pi Pico 2 W の水彩イラスト。同じパイプラインで別の部品にも適用できることを確認

緑色 PCB、castellated pin の金色、micro USB コネクタの位置、無線モジュールのシールド形状がそれぞれ識別できる。同じスクリプト、同じ denoise 0.9 で部品を入れ替えるだけでこの粒度の出力が再現できることが確認できた。

よくある質問

Q. GPU が VRAM 6GB しかなくても動きますか?

A. 動きます。SDXL を fp8 量子化(ComfyUI の起動オプションで --fp8_e4m3fn-unet を指定)で約 4GB に抑え、IP-Adapter を含めても VRAM 5.5GB 程度です。ただし ControlNet を同時にロードすると 6GB 上限に近づき不安定になります。

Q. 1 枚生成に何秒かかりますか?

A. GTX 1660 SUPER で 15 steps の txt2img なら約 90 秒、IP-Adapter 付きで約 120 秒、25 steps の img2img illustration なら約 120 秒です。クラウド GPU に比べると遅いですが、コストは電気代だけなので無制限に試行錯誤できます。

Q. denoise 0.9 が決め手とのことですが、なぜ 0.7 や 0.8 では効果が薄いのですか?

A. img2img では denoise が低いほど初期 latent(参照写真の構造)が結果に強く残ります。0.7-0.85 だと参照写真の写真感がうっすら残ってしまい、プロンプトのイラスト指定が押し負けます。0.9 で初期 latent の影響がほぼ「形のヒント」程度まで弱まり、スタイルはプロンプトが完全制御する状態になります。

Q. なぜ IP-Adapter Plus じゃなく素の IP-Adapter を試さないのですか?

A. 素の ip-adapter_sdxl_vit-h.safetensors は Plus に比べて style 転写が弱いと言われており、選択肢として残っています。ただし img2img + 高 denoise が「カスタムノード追加なし・追加モデル DL なし」という運用上のシンプルさで上回るので、現状はそちらを採用しています。

※本記事の手順は執筆時点(2026 年 5 月)で動作確認していますが、ComfyUI のバージョンや IP-Adapter のモデル更新によりそのままでは動かない場合があります。動かない場合は コメント欄でお知らせください。

まとめ

SDXL → IP-Adapter → img2img と機能を足すたびに、生成画像が「汎用ガジェット → 実機の写実 → 手書き風イラスト」と段階的に変化していく過程を、実際の生成画像 6 枚で見せてきた。

VRAM 6GB のマシンでは 「カスタムノード追加なし」「メモリ消費 txt2img 並み」のアプローチが結局シンプルで強かった。img2img + 高 denoise は新しい技術ではないが、IP-Adapter のスタイル転写問題を回避する手段として再評価する価値がある。

「機能を足せば足すほど良くなる」とは限らない。制約に合わせてアプローチを切り替える柔軟性が、ローカル環境での AI 画像生成では一番大事だった。

この記事が役に立ったら X(Twitter)でシェアしてもらえると喜びます。

関連記事

参考

※この記事は Claude Code を使った自動更新を試しています。

2026年4月29日水曜日

Raspberry Pi Pico 2 W + MicroPython で温湿度センサーを自作する【2026年版・Home Assistant に MQTT 送信】

※Claude Codeで記事の自動更新お試し中

Raspberry Pi Pico 2 W の水彩イラスト(緑色 PCB と castellated pin、無線モジュール、micro USB)

Raspberry Pi Pico 2 W が出てから「結局 ESP32 と何が違うんだ?」と聞かれることが増えました。RP2350 のデュアルコア Cortex-M33 + Wi-Fi/BLE が載った Pico 2 W で、温湿度センサー BME280 の値を Home Assistant に MQTT で送信し、Auto Discovery で勝手にセンサーが出てくるところまでを最短で通します。半田付け不要、ブレッドボードのみ。電子工作初学者でも 1〜2 時間で動きます。

この記事でできること

  • Raspberry Pi Pico 2 W に MicroPython を入れて Wi-Fi に繋げられる
  • BME280 で温度・湿度・気圧を I2C 経由で読める
  • Home Assistant に MQTT Discovery でセンサーが自動登録される(YAML 不要)
  • 必要なものは合計 2,500〜3,500円から
  • Pico W で詰まりがちな umqtt.simple のインストール方法と、無通信で切断される問題への対処がわかる

なぜ Pico 2 W なのか

ESP32 系で同じことができるのは事実ですが、Pico 2 W には MicroPython 公式サポートが手厚い・Thonny で REPL に即繋がる・RP2350 のデュアルコアで片方を BLE 受信に回せるといった違いがあります。Wi-Fi 4 + BLE 5.2 が両方乗っているので、後から「BLE で SwitchBot 温湿度計のデータも吸う」みたいな拡張がそのままできます。今回は MQTT 中心ですが、最後に拡張アイデアを置いておきます。

使ったもの

母艦側のソフトウェア環境:

  • MicroPython v1.24 以降(Pico 2 W 公式 UF2)
  • Thonny(REPL/ファイル転送/mip パッケージ管理を全部こなす)
  • Home Assistant(HAOS / Container どちらでも可)
  • Mosquitto(Home Assistant の Mosquitto broker アドオンが楽)

配線

BME280 は I2C で繋ぎます。Pico 2 W の I2C0 デフォルト(GP4=SDA / GP5=SCL)に合わせるとコードが短くなります。

Raspberry Pi Pico 2 W
3V3(OUT) → VIN
GND → GND
GP4 (SDA) → SDA
GP5 (SCL) → SCL
BME280 (I2C)

BME280 モジュールに 3.3V レギュレータが載っていれば 5V でも動きますが、ノイズと自己発熱(温度がやや高めに出る)を抑えるために 3V3(OUT) で給電するのを勧めます。

手順

1. MicroPython を Pico 2 W に書き込む

Pico 2 W の BOOTSEL ボタンを押しながら USB を挿すとマスストレージとして認識されます。ここに RP2350 用の最新 UF2 をドラッグ&ドロップ。

# raspberrypi.com/documentation/microcontrollers/micropython.html
# 「Raspberry Pi Pico 2 W」用 .uf2 を落として RPI-RP2 ドライブにコピー
# 自動で再起動して MicroPython が起動する

2. Thonny から umqtt.simple を入れる

古い記事だと upip でインストールしろと書いてありますが、2026 年現在は upip は廃止。代わりに mip を使います。Thonny のシェル(MicroPython 側)で:

>>> import mip
>>> mip.install("umqtt.simple")
>>> mip.install("umqtt.robust")  # 切断時の自動再接続用

Pico 2 W が Wi-Fi 接続前だと当然失敗するので、先にネットに繋ぐか、それが面倒なら PC で simple.py を落として Thonny でアップロードしても OK。

3. BME280 ドライバを入れる

MicroPython 用 BME280 ドライバはいくつかありますが、robert-hh/BME280 が安定。bme280_float.py を Thonny のファイルブラウザ経由で Pico に転送します。

4. main.py を書く

Wi-Fi 接続 → BME280 から読む → Home Assistant に Discovery 設定を一度送る → 60秒おきに値を publish、という最小構成にします。SECRETS は別ファイルに切り出すと git 管理しやすいですが、まずはベタ書きで動かします。

import network, time, ujson, machine
from umqtt.robust import MQTTClient
import bme280_float as bme280

WIFI_SSID = "YourSSID"
WIFI_PASS = "YourPass"
MQTT_HOST = "192.168.1.10"   # Home Assistant の IP
MQTT_USER = "pico"
MQTT_PASS = "picopass"
DEVICE_ID = "pico2w_livingroom"

# --- Wi-Fi ---
wlan = network.WLAN(network.STA_IF); wlan.active(True)
wlan.connect(WIFI_SSID, WIFI_PASS)
for _ in range(20):
    if wlan.isconnected(): break
    time.sleep(0.5)
print("ip:", wlan.ifconfig()[0])

# --- I2C / BME280 ---
i2c = machine.I2C(0, sda=machine.Pin(4), scl=machine.Pin(5), freq=100_000)
sensor = bme280.BME280(i2c=i2c)

# --- MQTT ---
c = MQTTClient(DEVICE_ID, MQTT_HOST, user=MQTT_USER, password=MQTT_PASS,
               keepalive=60)   # ←無通信切断対策
c.connect()

# --- HA Auto Discovery (一度だけ retain で送る) ---
def disc(component, name, key, unit, dev_class):
    topic = "homeassistant/sensor/{}/{}/config".format(DEVICE_ID, key)
    payload = {
        "name": name,
        "state_topic": "ontheh/{}/state".format(DEVICE_ID),
        "unit_of_measurement": unit,
        "value_template": "{{{{ value_json.{} }}}}".format(key),
        "unique_id": "{}_{}".format(DEVICE_ID, key),
        "device_class": dev_class,
        "device": {"identifiers":[DEVICE_ID], "name":"Pico 2W Livingroom",
                   "manufacturer":"Raspberry Pi", "model":"Pico 2 W"},
    }
    c.publish(topic, ujson.dumps(payload), retain=True)

disc("sensor", "リビング温度", "temp", "°C", "temperature")
disc("sensor", "リビング湿度", "hum",  "%",  "humidity")
disc("sensor", "リビング気圧", "pres", "hPa","pressure")

# --- メインループ ---
while True:
    t, p, h = sensor.read_compensated_data()
    payload = ujson.dumps({
        "temp": round(t, 2),
        "hum":  round(h, 2),
        "pres": round(p / 100, 2),    # Pa → hPa
    })
    c.publish("ontheh/{}/state".format(DEVICE_ID), payload)
    print(payload)
    time.sleep(60)

5. Home Assistant 側で確認

Mosquitto broker アドオンを起動してユーザーを作っておけば、main.py を実行した瞬間に Settings → Devices & services → MQTT に「Pico 2W Livingroom」がデバイスとして自動登録されます。YAML 編集も再起動も不要。

# MQTT を覗きたいときは Mosquitto アドオン経由でも OK
$ mosquitto_sub -h 127.0.0.1 -u pico -P picopass -t 'ontheh/#' -v
ontheh/pico2w_livingroom/state {"temp": 24.31, "hum": 53.78, "pres": 1011.42}

詰まりポイント

  • USB を繋いでも認識されない → 充電専用ケーブルあるある。データ線が結線されたケーブルに変える。
  • mip.installnot found → Wi-Fi 未接続。network.WLAN で接続してから実行する。
  • I2C デバイスが i2c.scan() に出てこない → BME280 のアドレスは 0x76 または 0x77。SDO の処理で変わる。bme280.BME280(i2c=i2c, address=0x77) を試す。
  • 数時間後に publish が止まる → ブローカーから keepalive で切られている。MQTTClient(..., keepalive=60) + umqtt.robust で自動再接続させるのが鉄板。
  • 温度が +2℃ ぐらい高めに出る → BME280 の自己発熱。長辺を立てる、5V→3V3 給電に変える、それでもズレるなら value_template でオフセット補正。
  • HA に出ない → Discovery トピックは必ず retain=True で送る。送り損ねた場合は HA を再起動するか、空ペイロード "" を送って消してから再送する。

ハック拡張アイデア

  • BLE モードを使って SwitchBot 温湿度計ハック: Pico 2 W の BLE 5.2 で SwitchBot のアドバタイズを直接受け、HA Bridge を経由せずに温湿度を吸い出す。OpenWonderLabs の公開仕様を読めば実装できます。
  • 0.96 インチ HiLetgo OLED ディスプレイ(SSD1306 / I2C 128×64)(Amazonで確認)を Pico の I2C1 (GP14/GP15) に追加して、温湿度をローカル表示する。BME280 と I2C アドレスを分けて同じバスに乗せても可。
  • RP2350 のもう一方のコアに _thread でリングバッファを置く: 片方のコアが I2C 読み出し、もう片方が MQTT 送信、を完全分離すると Wi-Fi 切断中もサンプリングが止まらない。Pico 1 ではここまでしんどかったのが楽になります。
  • HA の自動化と連動: 「湿度 60% を超えたら除湿器の SwitchBot プラグを ON」みたいな自動化が、Discovery で自動登録されたエンティティ名を参照するだけで書ける。

消費電力メモ

Pico 2 W を 5V USB 給電・60 秒 publish ループで放置すると、平均 70mA 前後(Wi-Fi 維持のため)。machine.deepsleep() でセンサー読み出しの瞬間だけ起こす運用にすると 10〜15mA まで下がりますが、Wi-Fi 再接続に毎回 3〜5 秒かかるので、1 分間隔のホームセンサーなら常時接続のままで実用です。

※本記事の手順・コードは執筆時点(2026 年 4 月)で動作確認していますが、ライブラリやハードのバージョンが変わるとそのままでは動かない場合があります。動かない場合は コメント欄でお知らせください。

まとめ

Raspberry Pi Pico 2 W + BME280 + MQTT Discovery で、半田付けなしの温湿度モニタを Home Assistant に組み込みました。umqtt.simple のインストール方法と keepalive を押さえれば、あとは公式チュートリアル系の罠もそんなにありません。同じ仕組みを増やしてリビング・寝室・玄関に置けば、家全体の温湿度マップが完成します。

この記事が役に立ったら X(Twitter)でシェアしてもらえると喜びます。

参考

ESP32-S3 + ESPHome で部屋のCO2モニタを自作する【2026年版・Home Assistant 連携】

この記事は、自宅の空気質を可視化したいエンジニア向けです。ESP32-S3 と CO2 センサ「SCD41」を ESPHome でつないで、CO2・温度・湿度を Home Assistant に流し込むまでを最短ルートで書きます。半田付けほぼナシ、ファームのコードも全公開です。(在宅ワーク中の「なんか頭がぼーっとする」問題を物理的に可視化したかった、というのが動機です。)

XIAO ESP32-S3 と SCD41 CO2 センサを 4 本のジャンパ線で I2C 接続した完成セットアップ

この記事でできること

  • ESP32-S3 で動くスタンドアロンの CO2 モニタが作れる(消費電力は数十 mW クラス)
  • ESPHome の YAML を書くだけで Home Assistant に自動統合される(コードは記事内で全部公開)
  • 必要なものは合計 4,000 円ちょっとから

使ったもの

全体の流れ

SCD41(CO2 + 温度 + 湿度)
▼ I2C(4 本接続)
XIAO ESP32-S3 + ESPHome
▼ Wi-Fi(ESPHome API)
Home Assistant(可視化・通知・自動化)

手順

1. 配線(4 本だけ)

SCD41 と XIAO ESP32-S3 を I2C で接続します。XIAO のデフォルト I2C は D4 = GPIO5(SDA)D5 = GPIO6(SCL)

SCD41 側 XIAO ESP32-S3 側
VDD3V3
GNDGND
SDAD4(GPIO5)
SCLD5(GPIO6)

2. ESPHome を準備

ESPHome は Home Assistant 公式の「マイコン用ファームウェア生成ツール」。Home Assistant の「ESPHome アドオン」を入れておくのが一番楽(HACS 不要)。ローカルで動かすなら pipx でも入る。

$ pipx install esphome
$ esphome version
2026.4.x

3. YAML を書く(コピペでOK)

以下を co2-monitor.yaml として保存。!secret 部分は自分の Wi-Fi に書き換えてください。

esphome:
  name: co2-monitor
  friendly_name: CO2 Monitor

esp32:
  board: seeed_xiao_esp32s3
  framework:
    type: arduino

logger:
api:
  encryption:
    key: "32 バイトの base64 キーを入れる"

ota:
  - platform: esphome
    password: "適当に"

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password

i2c:
  sda: GPIO5
  scl: GPIO6
  scan: true

sensor:
  - platform: scd4x
    co2:
      name: "CO2"
    temperature:
      name: "Temperature"
    humidity:
      name: "Humidity"
    update_interval: 60s

4. ファームを書き込む

USB-C で XIAO を PC につないで、初回はシリアル経由で焼きます。

$ esphome run co2-monitor.yaml
# シリアルポートを選んで Enter

2 回目以降は同じネットワーク上なら無線(OTA)で更新できます。これが ESPHome の最大の楽さ。

5. Home Assistant に統合

同じ Wi-Fi 上にあれば自動検出されます。検出されない場合は手動で:

  1. 「設定」→「デバイスとサービス」→「統合を追加」→「ESPHome」
  2. ホスト名 co2-monitor.local を入力
  3. YAML で設定した暗号化キーを入力 → 完了

CO2 / Temperature / Humidity の 3 センサが現れるので、ダッシュボードに「センサカード」「履歴グラフカード」を追加するだけで可視化完成です。

ここから先のハック

  • OLED 表示を追加: 0.96 インチ I2C OLED(SSD1306)を同じバスに繋いで現在の CO2 をその場で見せる。I2C なので配線追加なしでデバイスを増やせる
  • CO2 が高くなったら換気通知: Home Assistant のオートメーションで「1000 ppm が 3 分続いたら Slack / LINE 通知」を組む
  • L チカ警告: XIAO 内蔵 LED や WS2812B LED テープlight: コンポーネントで点灯。CO2 レベルで色を変える(青→緑→黄→赤)
  • バッテリ駆動: XIAO ESP32-S3 はリポ直結&充電回路つき。ディープスリープ+10 分おき計測で数日もつ

詰まりポイント

  • I2C で SCD41 が見えない: i2c: scan: true のログを確認。0x62 が見えれば成功。見えない場合は配線(特に SDA と SCL の逆挿し)を疑う
  • ESPHome のボード指定: board: esp32-s3-devkitc-1 でも動くが、XIAO 専用の seeed_xiao_esp32s3 のほうが GPIO マッピングが正しい
  • 初回の数値が暴れる: SCD41 は電源投入から最初の数分は校正中で値が安定しない。常時通電で運用するのが前提

※本記事の手順・コードは執筆時点(2026 年 4 月)で動作確認していますが、ライブラリやハードのバージョンが変わるとそのままでは動かない場合があります。動かない場合は コメント欄でお知らせください。

まとめ

ESP32-S3 + SCD41 の組み合わせは、半田付けゼロ・コード 80 行で「自宅の CO2 を Home Assistant に流す」までいけます。市販の CO2 モニタを買うより安く、しかもオートメーションで「換気して」と通知させたり、エアコンの強弱を切り替えたり、好きなだけ拡張できる。これが自作の楽しさ。

この記事が役に立ったら X(Twitter)でシェアしてもらえると喜びます。

よくある質問

Q. SCD41 が I2C で見えないときは?

A. i2c: scan: true のログを確認し、0x62 が見えなければ配線(SDA/SCL の逆挿し)を疑ってください。XIAO ESP32-S3 のデフォルト I2C は D4=GPIO5(SDA)D5=GPIO6(SCL) です。

Q. 数値が安定しないときは?

A. SCD41 は電源投入後の最初の数分は校正中で値が安定しません。常時通電で運用するのが前提です。「ウォームアップ中」と割り切って数分待ちましょう。

Q. ESPHome のボード指定はどれが正しい?

A. seeed_xiao_esp32s3 を使います。汎用の esp32-s3-devkitc-1 でも動きますが、XIAO 専用指定のほうが GPIO マッピングが正しく当たります。

Q. バッテリ駆動できますか?

A. はい。XIAO ESP32-S3 はリポバッテリ直結&充電回路つきで、ディープスリープ+10 分おき計測で数日もちます。

関連記事

参考

※この記事は Claude Code を使った自動更新を試しています。

SwitchBot × IFTTT で帰宅前にエアコンを自動 ON【GPS トリガー連携版】

※Claude Codeで記事の自動更新お試し中

この記事は、「帰宅前に自動でエアコンを付けたい」「外部サービスと連携したい」方向けです。IFTTT と SwitchBot を組み合わせることで、GPS 位置情報・天気・時刻など SwitchBot アプリ単体では使えないトリガーでエアコンを操作できます。

3つのアプローチ比較

同じことを実現する方法は3つあります。この記事では IFTTT × SwitchBot の連携方法を解説します。

(1) Raspberry Pi
+ cron
(2) SwitchBot
オートメーション
(3) IFTTT
連携
(本記事)
追加ハードRaspberry Pi 必要不要不要
セットアップPython 実装
+ cron 設定
アプリのみ
(最も簡単)
IFTTTアカウント
+アプレット設定
トリガー制限なし
(Python で自由に)
温度・時刻・
在宅検知など
GPS・天気・
他サービス連携が強み
コストPi の電気代のみ
(約1〜2W)
無料無料プランは
2アプレットまで
ロジックの複雑さヒステリシス・ログ・
拡張など自由
AND/OR 程度IF→THEN の
単純なルール
クラウド依存SwitchBot API のみSwitchBot
クラウドのみ
IFTTT +
SwitchBot クラウド

「アプリだけで済ませたい」なら SwitchBot オートメーション版、「複雑なロジックを書きたい」なら Raspberry Pi 版も参照してください(記事末のシリーズ一覧)。

この記事でできること

  • IFTTT と SwitchBot を連携してエアコンを操作できる
  • 「自宅から 500m 圏内に入ったらエアコン ON」のような GPS トリガーが作れる
  • 天気・時刻・他サービスなど SwitchBot アプリ外のトリガーが利用できる

使ったもの

  • SwitchBot ハブ2(Amazonで確認)— Hub シリーズが IFTTT 連携に対応
  • IFTTT アカウント(無料プランで可、ただし後述の制限あり)
  • スマートフォン(iOS / Android)

注意:IFTTT 無料プランは有効化できるアプレット数が2つまでです。3つ以上使う場合は有料プラン(Pro、月額約600円〜)が必要になります。

事前準備:SwitchBot アプリでエアコンを登録する

Hub 2 でエアコンを操作するには、まずアプリでエアコンの赤外線コードを学習させます。

  1. SwitchBot アプリ → ホーム右上の「+」→「赤外線リモコンを追加」
  2. 「エアコン」→ メーカーを選択(プリセットにあればそのまま使用)
  3. プリセットにない場合は「学習リモコン」で実機リモコンの信号を取り込む
  4. アプリからオン・オフできることを確認する

この操作でエアコンが SwitchBot クラウドに「仮想赤外線リモコン」として登録されます。どの方法でもこの準備は共通で必要です。

IFTTT と SwitchBot の連携手順

1. IFTTT アカウントを作成する

IFTTT の公式サイトでアカウントを作成します。Google アカウントでのサインアップが手軽です。

2. SwitchBot サービスを連携する

IFTTT の検索で「SwitchBot」を探し「Connect」→ SwitchBot アカウントでログインして連携を承認します。

3. アプレットを作成する(例:帰宅前エアコン ON)

「Create」→「If This」→「Location」→「You enter an area」で自宅周辺の円を設定(半径 500m 以上推奨)。「Then That」→「SwitchBot」→「Control your device」でエアコンを ON に設定します。

4. エアコンを OFF にするアプレットも作成する

「You exit an area(エリアから出たら)」または「温度が下がったら」など、状況に応じた OFF ルールを別途作ります。IFTTT 無料プランの2アプレット枠はこれで埋まります。

GPS トリガーの精度について

IFTTT の位置情報トリガーはスマートフォンの GPS を使うため、精度は 100〜500m 程度です。設定エリアの半径を 500m 以上にしておくと誤作動が減ります。また、応答に1〜5分程度の遅延が発生することがあります。「帰宅15分前に ON」を狙うなら半径を 2〜3km にするのが現実的です。

IFTTT 連携の制限

  • 無料プランはアプレット2つまで(ON/OFF で2つ使い切る)
  • IF→THEN の単純なルールのみ(AND/OR 条件は Pro プラン)
  • SwitchBot の IFTTT 連携は Hub シリーズのみ対応(Hub Mini・Hub 2 は対応、独立したセンサー系は不可)
  • IFTTT サービス障害時は動作しない(クラウド依存が2重になる)

※本記事の手順・コードは執筆時点(2026 年 4 月)で動作確認していますが、ライブラリやハードのバージョンが変わるとそのままでは動かない場合があります。動かない場合は コメント欄でお知らせください。

まとめ

IFTTT × SwitchBot で、GPS 位置情報をトリガーにした「帰宅前エアコン自動 ON」が手軽に作れます。SwitchBot アプリ単体では対応できない外部サービス連携が必要な場合に選ぶ方法です。ただし無料プランの2アプレット制限と応答遅延は事前に把握しておくことをおすすめします。

この記事が役に立ったら X(Twitter)でシェアしてもらえると喜びます。

参考

このシリーズの記事

SwitchBot のオートメーションで室温連動エアコン制御をアプリだけで設定する【ノーコード版】

※Claude Codeで記事の自動更新お試し中

この記事は、プログラミングなしでエアコンを温度連動で自動制御したい方向けです。SwitchBot Hub 2 のアプリ内「オートメーション」機能だけで、室温が設定温度を超えたらエアコンを自動でオン・オフする設定が5分で完結します。

3つのアプローチ比較

同じことを実現する方法は3つあります。この記事では SwitchBot アプリのオートメーション機能 を使う方法を解説します。

(1) Raspberry Pi
+ cron
(2) SwitchBot
オートメーション
(本記事)
(3) IFTTT
連携
追加ハードRaspberry Pi 必要不要不要
セットアップPython 実装
+ cron 設定
アプリのみ
(最も簡単)
IFTTTアカウント
+アプレット設定
トリガー制限なし
(Python で自由に)
温度・時刻・
在宅検知など
GPS・天気・
他サービス連携が強み
コストPi の電気代のみ
(約1〜2W)
無料無料プランは
2アプレットまで
ロジックの複雑さヒステリシス・ログ・
拡張など自由
AND/OR 程度IF→THEN の
単純なルール
クラウド依存SwitchBot API のみSwitchBot
クラウドのみ
IFTTT +
SwitchBot クラウド

「GPS や外部サービス連携」なら IFTTT 版、「複雑なロジックを Python で書きたい」なら Raspberry Pi 版も参照してください(記事末のシリーズ一覧)。

この記事でできること

  • SwitchBot アプリだけで温度連動エアコン自動制御が設定できる
  • Raspberry Pi・プログラミングは一切不要
  • 設定時間は5分以内、スマートフォン1台で完結

使ったもの

  • SwitchBot ハブ2(Amazonで確認)— 温湿度センサー内蔵の赤外線ハブ。これ1台で完結します
  • エアコン(赤外線リモコン対応であればメーカー不問)
  • スマートフォン(iOS / Android)

事前準備:SwitchBot アプリでエアコンを登録する

Hub 2 でエアコンを操作するには、まずアプリでエアコンの赤外線コードを学習させます。

  1. SwitchBot アプリ → ホーム右上の「+」→「赤外線リモコンを追加」
  2. 「エアコン」→ メーカーを選択(プリセットにあればそのまま使用)
  3. プリセットにない場合は「学習リモコン」で実機リモコンの信号を取り込む
  4. アプリからオン・オフできることを確認する

この操作でエアコンが SwitchBot クラウドに「仮想赤外線リモコン」として登録されます。どの方法でもこの準備は共通で必要です。

オートメーションの設定手順

1. オートメーション画面を開く

SwitchBot アプリ下部メニューの「オートメーション」タブをタップします。右上の「+」で新規作成します。

2. 条件(IF)を設定する — エアコンをONにするルール

「条件を追加」→「デバイスの状態」→「Hub 2」→「温度」→「28℃ 以上」を選択します。

3. アクション(THEN)を設定する

「アクションを追加」→「デバイスを制御」→「エアコン」→「電源オン」を選択します。必要であれば設定温度・モードも指定できます。

4. オートメーションを保存・有効化する

名前(例:「暑くなったらエアコンON」)を入力して保存し、トグルをオンにします。

5. OFFにするルールも追加する

上記と同じ手順で「25℃ 以下になったらエアコンOFF」のルールを別途作成します。ON だけでは永久に動き続けるため、必ずセットで設定してください。ON / OFF の温度に差をつけることで(ヒステリシス)、温度がしきい値付近でエアコンが頻繁にオン・オフするのを防げます。

応用:時間帯制限を加える

深夜に動作させたくない場合は、条件に「時刻:8:00〜23:00」を AND 条件で追加します。「デバイスの状態」と「時刻」を両方条件に入れると、両方が満たされたときだけ動作します。

SwitchBot オートメーションの制限

  • 条件は AND/OR の組み合わせまで。複雑な分岐や計算はできない
  • 動作ログは「アクティビティ履歴」で確認できるが、CSV 出力などはできない
  • GPS 位置情報トリガーは非対応(帰宅前に自動 ON したい場合は IFTTT 版を参照)

※本記事の手順・コードは執筆時点(2026 年 4 月)で動作確認していますが、ライブラリやハードのバージョンが変わるとそのままでは動かない場合があります。動かない場合は コメント欄でお知らせください。

まとめ

SwitchBot オートメーションは追加費用・追加ハードなしで温度連動エアコン制御が実現できる最もシンプルな方法です。まずここから始めて、物足りなくなったら IFTTT や Raspberry Pi の方法に移行するのがおすすめです。

この記事が役に立ったら X(Twitter)でシェアしてもらえると喜びます。

参考

このシリーズの記事

SwitchBot API × Raspberry Pi 5 で部屋のエアコンを自動制御する【2026年版】

※Claude Codeで記事の自動更新お試し中

この記事は、SwitchBot Hub 2 でエアコンを温度連動で自動制御したいエンジニア向けです。Python スクリプトと cron を使って、室温に応じてエアコンを自動でオン・オフする仕組みを Raspberry Pi 5 上に構築します。

3つのアプローチ比較

同じことを実現する方法は3つあります。この記事では Raspberry Pi + Python + cron の方法を解説します。

(1) Raspberry Pi
+ cron
(本記事)
(2) SwitchBot
オートメーション
(3) IFTTT
連携
追加ハードRaspberry Pi 必要不要不要
セットアップPython 実装
+ cron 設定
アプリのみ
(最も簡単)
IFTTTアカウント
+アプレット設定
トリガー制限なし
(Python で自由に)
温度・時刻・
在宅検知など
GPS・天気・
他サービス連携が強み
コストPi の電気代のみ
(約1〜2W)
無料無料プランは
2アプレットまで
ロジックの複雑さヒステリシス・ログ・
拡張など自由
AND/OR 程度IF→THEN の
単純なルール
クラウド依存SwitchBot API のみSwitchBot
クラウドのみ
IFTTT +
SwitchBot クラウド

「アプリだけで済ませたい」なら SwitchBot オートメーション版、「GPS 連動や外部サービス連携」なら IFTTT 版も参照してください(記事末のシリーズ一覧)。

この記事でできること

  • SwitchBot API v1.1 を Python から叩いてデバイスを操作できる
  • Raspberry Pi 5 上で室温を5分ごとに取得し、条件分岐でエアコンを制御できる
  • ヒステリシスつきの温度制御で、頻繁なオン・オフを防げる

使ったもの

構成の概要

Raspberry Pi 自体には赤外線モジュールは不要です。Pi は SwitchBot API に HTTPS でコマンドを送るだけで、実際に赤外線を飛ばすのは SwitchBot Hub 2 が担います。

Raspberry Pi 5

HTTPS(SwitchBot API v1.1)
SwitchBot Hub 2

赤外線
エアコン

事前準備:SwitchBot アプリでエアコンを登録する

Hub 2 でエアコンを操作するには、まずアプリでエアコンの赤外線コードを学習させます。

  1. SwitchBot アプリ → ホーム右上の「+」→「赤外線リモコンを追加」
  2. 「エアコン」→ メーカーを選択(プリセットにあればそのまま使用)
  3. プリセットにない場合は「学習リモコン」で実機リモコンの信号を取り込む
  4. アプリからオン・オフできることを確認する

この操作でエアコンが SwitchBot クラウドに「仮想赤外線リモコン」として登録されます。どの方法でもこの準備は共通で必要です。

事前準備②:SwitchBot API トークンの取得

SwitchBot アプリのバージョン表示を 10回タップ するとデベロッパーオプションが開きます。

  1. SwitchBot アプリ → プロフィール → 設定
  2. 「アプリバージョン」を10回タップ
  3. 「開発者向けオプション」→「トークンを取得」
  4. Token と Client Secret をメモする
export SWITCHBOT_TOKEN="your_token_here"
export SWITCHBOT_SECRET="your_secret_here"

デバイス一覧の取得

エアコンは通常の deviceList ではなく infraredRemoteList に入っています。まず両方を確認します。

import hashlib, hmac, base64, time, uuid, requests, os

TOKEN  = os.environ["SWITCHBOT_TOKEN"]
SECRET = os.environ["SWITCHBOT_SECRET"]

def get_headers():
    t   = str(round(time.time() * 1000))
    n   = str(uuid.uuid4())
    msg = TOKEN + t + n
    sig = base64.b64encode(
        hmac.new(SECRET.encode(), msg.encode(), hashlib.sha256).digest()
    ).decode()
    return {
        "Authorization": TOKEN,
        "t": t, "nonce": n, "sign": sig,
        "Content-Type": "application/json",
    }

r = requests.get("https://api.switch-bot.com/v1.1/devices", headers=get_headers())
body = r.json()["body"]

print("--- 物理デバイス(Hub 2 等)---")
for d in body["deviceList"]:
    print(d["deviceId"], d["deviceName"], d["deviceType"])

print("--- 赤外線リモコン(エアコン等)---")
for d in body["infraredRemoteList"]:
    print(d["deviceId"], d["deviceName"], d["remoteType"])

出力例:

--- 物理デバイス ---
YYYYYYYY  リビングHub2        Hub 2

--- 赤外線リモコン ---
XXXXXXXX  リビングのエアコン   Air Conditioner

室温の取得

def get_temperature(device_id):
    url = f"https://api.switch-bot.com/v1.1/devices/{device_id}/status"
    r = requests.get(url, headers=get_headers())
    body = r.json()["body"]
    return body["temperature"], body["humidity"]

temp, hum = get_temperature("YYYYYYYY")  # Hub 2 のデバイスID
print(f"室温: {temp}℃  湿度: {hum}%")

エアコンの操作

def control_ac(device_id, command):
    url = f"https://api.switch-bot.com/v1.1/devices/{device_id}/commands"
    payload = {
        "command": command,       # "turnOn" or "turnOff"
        "parameter": "default",
        "commandType": "command",
    }
    r = requests.post(url, headers=get_headers(), json=payload)
    return r.json()["statusCode"]

AC_ID    = "XXXXXXXX"   # infraredRemoteList で確認したエアコンの ID
HUB_ID   = "YYYYYYYY"   # deviceList で確認した Hub 2 の ID
ON_TEMP  = 28
OFF_TEMP = 25

temp, _ = get_temperature(HUB_ID)
if temp >= ON_TEMP:
    control_ac(AC_ID, "turnOn")
    print(f"{temp}℃ → エアコン ON")
elif temp <= OFF_TEMP:
    control_ac(AC_ID, "turnOff")
    print(f"{temp}℃ → エアコン OFF")

ON_TEMP と OFF_TEMP に差をつける(ヒステリシス)ことで、28℃ をまたいだだけでエアコンが連続オン・オフするのを防いでいます。

cron で自動実行する

$ crontab -e

# 5分ごとに室温チェック
*/5 * * * * /usr/bin/python3 /home/pi/ac_control.py >> /home/pi/ac_control.log 2>&1

※本記事の手順・コードは執筆時点(2026 年 4 月)で動作確認していますが、ライブラリやハードのバージョンが変わるとそのままでは動かない場合があります。動かない場合は コメント欄でお知らせください。

まとめ

SwitchBot Hub 2 があれば Raspberry Pi に赤外線モジュールは不要で、Python から API を叩くだけでエアコンを自動制御できます。ヒステリシス・ログ・時間帯制限など SwitchBot アプリでは難しいロジックが自由に実装できるのが最大のメリットです。

この記事が役に立ったら X(Twitter)でシェアしてもらえると喜びます。

参考

このシリーズの記事