SonoffドングルとDockerでZigbee2MQTTを構築:完全ローカル動作のスマートホーム環境

HomeLab tutorial - IT technology blog
HomeLab tutorial - IT technology blog

Tuya、Aqara、Philips Hueなどの独自規格ハブは、頻繁に外部クラウドへ通信を行います。そのため、インターネット接続が途切れると自動化された照明制御まで停止してしまいます。さらにクラウドとの往復通信により、壁スイッチの操作時に300〜800msもの不快な遅延が生じがちです。Zigbeeネットワークをローカルで完結させれば、こうしたボトルネックを解消でき、センサーデータの外部流出を防ぎつつコマンドの応答遅延を15ms未満まで大幅に短縮できます。

Sonoff Zigbee 3.0 USB Dongle PlusとDocker上のZigbee2MQTTを組み合わせることで、手頃なハードウェア構成でありながらエンタープライズ級の安定性を実現できます。筆者のホームラボ環境では、Hue電球、Sonoffドアセンサー、Aqara環境センサーなど、3フロアにまたがる85台以上のデバイスをこの同一構成で統括しており、14か月以上にわたりパケットロスゼロで稼働し続けています。

クイックスタート(5分)

コンテナを設定する前に、Sonoffドングルをホストマシンへ接続してください。その際、一時的なデバイスノードではなく、固定されたハードウェアパスを特定する必要があります。

ステップ 1: アダプターのIDを確認する

次のコマンドを実行して、Linuxのシリアルサブシステムを照会します:

ls -la /dev/serial/by-id/

ターミナルに以下のような形式の出力が表示されます:

usb-ITead_Sonoff_Zigbee_3.0_USB_Dongle_Plus_e850b6919db1eb11b512e0e0-if00-port0 -> ../../ttyUSB0

シンボリックリンクの完全なパス(/dev/serial/by-id/usb-ITead_Sonoff_...)をコピーしてください。/dev/ttyUSB0のような生のデバイスパスを直接バインドしてはいけません。別のシリアル周辺機器を追加接続したりホストを再起動したりした際にLinuxカーネルがデバイス番号を再割り当てし、コンテナスタックがクラッシュする原因になります。

ステップ 2: ディレクトリ構造を作成する

mkdir -p ~/homelab/zigbee2mqtt/data
mkdir -p ~/homelab/mosquitto/config
mkdir -p ~/homelab/mosquitto/data
mkdir -p ~/homelab/mosquitto/log
cd ~/homelab

ステップ 3: Mosquittoの設定

Mosquitto 2.0以降では、デフォルトで認証なしのトラフィックが拒否されます。~/homelab/mosquitto/config/mosquitto.confを作成し、以下の基本設定を記述します:

listener 1883
allow_anonymous true
persistence true
persistence_location /mosquitto/data/
log_dest file /mosquitto/log/mosquitto.log

隔離されたローカルサブネット外にもMQTTを公開する場合は、allow_anonymous trueの代わりにパスワードファイルによる認証を設定してください。

ステップ 4: Docker Composeで起動する

~/homelab/<a href="https://itnotes.dev/ja/docker-compose%e3%82%92%e5%8d%92%e6%a5%ad%e3%81%97%e3%82%88%e3%81%86%ef%bc%9apodman-quadlet%e3%81%ab%e3%82%88%e3%82%8bhomelab%e3%82%b3%e3%83%b3%e3%83%86%e3%83%8a%e7%ae%a1%e7%90%86/">docker-compose.yml</a>にサービス定義を記述します:

services:
  mqtt:
    image: eclipse-mosquitto:2
    container_name: mosquitto
    restart: unless-stopped
    ports:
      - "1883:1883"
    volumes:
      - ./mosquitto/config:/mosquitto/config
      - ./mosquitto/data:/mosquitto/data
      - ./mosquitto/log:/mosquitto/log

  zigbee2mqtt:
    image: koenkk/zigbee2mqtt:latest
    container_name: zigbee2mqtt
    restart: unless-stopped
    volumes:
      - ./zigbee2mqtt/data:/app/data
      - /run/udev:/run/udev:ro
    ports:
      - "8080:8080"
    environment:
      - TZ=UTC
    devices:
      - /dev/serial/by-id/usb-ITead_Sonoff_Zigbee_3.0_USB_Dongle_Plus_e850b6919db1eb11b512e0e0-if00-port0:/dev/ttyACM0
    depends_on:
      - mqtt

コンテナを立ち上げます:

docker compose up -d

ブラウザでhttp://<サーバーのIPアドレス>:8080にアクセスします。Zigbee2MQTTの管理パネルがすぐに表示されます。

詳細ガイド

コンテナの起動自体は5分もかかりません。しかし、コンクリート壁や電波干渉がある環境で安定した通信を維持するには、低レイヤーの設定調整が不可欠です。

1. デバイスマッピングとシリアルポートの権限

Linuxはブート時の検出順序に応じてttyUSB0などの汎用ノードを動的に割り当てます。UPS監視装置やZ-Waveコントローラーを接続するだけで、ドングルが意図せずttyUSB1に切り替わってしまうことがあります。/dev/serial/by-id/経由でバインドすれば、コンテナが常に物理的なTexas Instruments製無線チップへ確実にアクセスできるようになります。

コンテナログにError: Resource temporarily unavailable Cannot lock portと出力される場合、Docker実行ユーザーにシリアルグループへのアクセス権がありません。ユーザーをdialoutグループに追加してください:

sudo usermod -aG dialout $USER

2. `configuration.yaml`のセキュリティと最適化

初期起動時、コンテナは~/homelab/zigbee2mqtt/data/configuration.yamlを生成します。その内容を以下の本番運用向け設定に置き換えます:

homeassistant:
  enabled: true
permit_join: false
mqtt:
  base_topic: zigbee2mqtt
  server: 'mqtt://mqtt:1883'
serial:
  port: /dev/ttyACM0
  adapter: zstack
frontend:
  port: 8080
advanced:
  channel: 25
  network_key: GENERATE
  log_level: info
  pan_id: GENERATE
  ext_pan_id: GENERATE

permit_join: falseが設定されていることを必ず確認してください。実際にデバイスを追加する作業時以外は、ペアリングモードを常に無効化しておきます。許可状態のまま放置すると、近隣のデバイスや意図しないセンサーが勝手にコーディネーターへ接続されてしまう恐れがあります。

3. RF干渉:Wi-FiチャンネルとZigbeeチャンネルの重複回避

両プロトコルは同じ2.4GHz帯の周波数を共有しています。Wi-Fiのチャンネル幅は広く(20〜40MHz)、帯域の狭いZigbeeの電波(わずか2MHz幅)を容易にかき消してしまいます。Zigbeeのデフォルト設定であるチャンネル11は、Wi-Fiのチャンネル1と完全に重複しています。

Zigbeeをチャンネル25へ変更することで、Wi-Fiチャンネル11のさらに上にある電波干渉の少ない帯域へ逃がすことができます。このシンプルな変更だけで、住宅街などの環境におけるセンサーパケットの再送率が70%以上低減することも珍しくありません。

応用的な使い方

デバイス間の直接バインド(Direct Binding)

Home Assistantによる一元的な自動化は、条件に応じたスケジュール管理に非常に便利です。しかし、サーバーがクラッシュしたりカーネル更新で再起動している間はどうなるでしょうか。寝室のスイッチは、サーバーの状態にかかわらず天井照明を操作できるべきです。

Zigbeeはネイティブで直接バインド(Direct Binding)に対応しています。Zigbee2MQTTのWeb UIで以下の操作を行います:

  1. ワイヤレススイッチを選択します。
  2. Bindタブを開きます。
  3. 操作対象の照明器具とクラスター(通常はOnOffまたはLevelControl)を選択します。
  4. Bindをクリックします。

これでスイッチから無線経由で照明へ直接コマンドが送られるようになります。ホストOS、Dockerエンジン、MQTTブローカーを完全にバイパスするため、わずか約10msでコマンドが実行されます。

OTA(Over-the-Air)ファームウェア更新

Zigbee2MQTTは、IKEA、Sonoff、Philips Hueなどのベンダーが提供する署名済みファームウェアのダウンロードに対応しています。Web UIのOTAタブを開き、Check allを選択します。定期的なファームウェア更新により、ベンダー固有のスリープサイクルの不具合解消、メッシュルーティングの切断防止、バッテリー残量表示精度の改善などが期待できます。

メッシュトポロジーの可視化

Map画面では、全ノード間のリアルタイムなLQI(Link Quality Indicator: リンク品質指標)スコアが視覚化されます。LQI値が100以上であれば通信状態は良好です。40未満まで低下すると、パケット損失やトリガーの遅延が発生しやすくなります。末端デバイスのLQIが低い場合は、中間にコンセント直結型のZigbeeスマートプラグを設置し、メッシュリピーターとして機能させてください。

実践的なノウハウ

必ずUSB 2.0延長ケーブルを使用する

ZigbeeコーディネーターをホストのマザーボードやRaspberry PiのUSB 3.0ポートへ直接挿してはいけません。シールド処理が不十分なUSB 3.0コントローラーチップは、2.4〜2.5GHz帯全体に広帯域RFノイズを放射します。このノイズフロアの上昇により、Zigbeeの受信感度が著しく低下します。1mほどのUSB 2.0延長ケーブルを挟んでアンテナを基板のノイズ源から物理的に離すだけで、受信感度を劇的に改善できます。

Sonoffドングルの適切なモデル選択(Dongle-P vs Dongle-E)

Sonoffからは似た名称で異なるハードウェアリビジョンが提供されています:

  • モデル「ZBDongle-P」(推奨): Texas Instruments製CC2652Pチップを搭載。長年の実績があり安定性に優れたzstackドライバーで動作します。確実な安定性を求めるならこのモデルを選んでください。
  • モデル「ZBDongle-E」: Silicon Labs製EFR32MG21チップを搭載。新しいemberアダプタードライバーが必要です。動作自体はしますが、Zigbee2MQTTにおける運用実績はDongle-Pほど長くありません。

設定ファイルとデータベースの自動バックアップ

ネットワーク暗号化キー、IEEEアドレステーブル、デバイス登録情報は、マウントしたボリューム内の以下の3つの主要ファイルに保存されています:

  • data/configuration.yaml
  • data/database.db
  • data/coordinator_backup.json

cronを使用してホスト側で日次バックアップを設定しておきましょう:

tar -czf ~/backups/z2m-$(date +%F).tar.gz ~/homelab/zigbee2mqtt/data

このアーカイブと固定されたハードウェアパスがあれば、新しいサーバーへのコーディネーター移行作業も2分足らずで完了します。照明スイッチや各種センサーを再ペアリングする必要は一切ありません。

Share: