跳转至

Zigbee Mesh 拓扑优化:从 20 台到 100 台的稳定性调优

家里第一个 Zigbee 设备是床头的一个传感器,配好就能用。第 10 个设备开始,我还会看看 z2m 面板确认一下。第 30 个设备那天凌晨,卧室里的智能窗帘电机、走廊的灯、卫生间的传感器在 3 分钟内陆续离线,我花了 4 个小时才发现——不是设备坏了,是路由拓扑崩了。

那之后我花了整整一年,从 30 台设备慢慢扩到 87 台(现在稳定运行 6 个月没再掉过),把 Mesh 拓扑优化这件事摸透了。这篇不重复入门,也不讲网关选型和配对流程,只讲设备多了之后怎么让网络不崩。所有数据都是我家现网的实测值,配置片段都可以直接抄。

1. Zigbee Mesh 三分类:先把设备按角色分对

Zigbee Mesh 里每台设备都是三类之一:协调器(Coordinator)、路由器(Router / Full-Function Device)、终端(End Device / Reduced-Function Device)。这三类角色的分工决定了整个网络的稳定性。绝大多数 Mesh 崩盘的根源,都是没把这三类分清楚。

1.1 协调器(Coordinator):网络的心脏,唯一一台

协调器是整个网络的根,PAN ID 和网络密钥都存在它里面。它只做三件事:

  • 维护网络路由表
  • 授权新设备加入(permit join)
  • 转发来自 HA/MQTT 的下行命令

关键点:协调器不应该做路由器的角色。SLZB-06P、CC2652P 这类协调器虽然有转发能力,但让设备直接连协调器做第一跳时,如果协调器同时还在处理 Mesh 路由决策,负载会飙。经验上:87 台设备里协调器直连设备控制在 15 台以内,其余通过路由器跳入。

1.2 路由器(Router):网络的中转站

路由器就是"有源设备"——接市电,7x24 小时在线,随时可以转发数据。Zigbee 里的路由不是可选的,只要两台设备在邻居表里,它们就互相转发。

适合当路由器的设备:

  • 智能插座(米家、Aqara、Tuya 系)——最便宜的路由器
  • 网关类(Aqara M1S / M2、米家多模网关、Sonoff Zigbee Bridge)——本身就是路由器
  • 智能灯泡(飞利浦 Hue、Yeelight、IKEA TRÅDFRI)——插电灯泡几乎都能做路由
  • 智能窗帘电机(米家、Aqara)——电机接市电,可以做路由
  • 智能开关(无线版通常不能,插卡版能)
  • Hub / 网关类的所有设备

不能当路由器的设备:

  • 电池传感器(温湿度、门窗、水浸、人体、烟感)
  • 无线开关(Aqara 无线开关、米家开关——那些按键式的)
  • 智能门锁(一般内置电池)
  • 遥控器 / 键控(都是低功耗设计)

1.3 终端(End Device):网络的末端

终端设备不转发数据,只跟父节点通信。它们大部分时间休眠,唤醒时才发包。所有电池供电的设备都应该是终端。

这里最容易翻车:有些设备(比如 Aqara 温湿度传感器)出厂就设计成终端,但你在 z2m 里手动"改"成 router,或者它被别的设备错当成 router 拉去转发,会立即引发两个问题——电池掉电加快 + 网络拓扑抖动。

1.4 设备分类决策表

拿到一个 Zigbee 设备,先看这几个特征:

特征 判断为
需要接入电源适配器 路由器
内置电池 / 纽扣电池 终端
官方文档说 "Repeater" 或 "Router" 路由器
官方文档说 "Battery-powered" 终端
智能插座 / 网关 路由器
无线按键 / 传感器 终端
智能窗帘电机(插电) 路由器
智能门锁 终端(不管插电池)

分类完成后,在 z2m 里用 zigbee2mqtt_get_metadata <ieee> 确认每个设备的 type 字段是 Router 还是 EndDevice。发现不一致的直接改:

# /data/configuration.yaml
devices:
  '0x00158d0001a2b3c4':
    friendly_name: '客厅温湿度'
    powered_by_battery: true   # 强制为电池设备
  '0x00158d0001b2c4d5':
    friendly_name: '玄关插座A'
    powered_by_battery: false  # 强制为电源设备

改完重启 z2m,让它重新从协调器拿设备属性。这一步是基础,87 台设备里我至少有 4 台被 z2m 或 HA 错标了角色。

2. 我家 87 台设备的现状——跳数矩阵与 RSSI 分布

先说说我家现网的数据。硬件是 SLZB-06P + Z2M 1.40.2 + HA 2025.x,家庭面积 120 平,三室两厅,Zigbee 设备分布:

  • 客厅:26 台(灯 6、插座 5、传感器 8、窗帘 2、网关 2、开关 3)
  • 卧室:18 台(灯 4、插座 3、传感器 6、窗帘 1、网关 1、开关 3)
  • 卫生间:9 台(灯 2、插座 2、传感器 5)
  • 厨房:6 台(灯 1、插座 2、传感器 3)
  • 玄关 / 走廊:15 台(灯 3、插座 2、传感器 5、开关 3、门禁 1、网关 1)
  • 阳台:6 台(灯 1、插座 2、传感器 3)
  • 协调器位置:客厅电视柜上方 1.5 米(离路由器 1.5 米,避开路由器信号干扰区)

2.1 路由器分布图

87 台设备里有 22 台路由器,其中协调器 1 台,路由器 21 台。分布:

客厅:协调器 + 智能插座 ×4 + 窗帘 ×1 + 网关 ×2 = 8 台路由器
卧室:智能插座 ×2 + 窗帘 ×1 + 网关 ×1 + 智能灯泡 ×3 = 7 台路由器
卫生间:智能插座 ×1 + 智能灯泡 ×1 = 2 台路由器
厨房:智能插座 ×1 + 智能灯泡 ×1 = 2 台路由器
玄关:智能插座 ×1 + 网关 ×1 = 2 台路由器
阳台:智能插座 ×1 = 1 台路由器

2.2 路由跳数矩阵

我用 HA 的 sensor.zigbee_hops(通过 zigbee2mqtt_get_metadata 定期采集)统计每台设备到协调器的跳数。理想分布:

跳数 设备数 占比
1 跳 18 20.7%
2 跳 47 54.0%
3 跳 19 21.8%
4 跳 3 3.4%
5+ 跳 0 0%

3 跳以内占 96.6%,4 跳的 3 台都在阳台尽头,勉强稳定。这个分布是我调到现在的结果——跳数超过 3 跳的设备,掉线率会比 2 跳设备高 3-5 倍。

2.3 RSSI 分布

RSSI(接收信号强度指示)是 dBm,越接近 0 越强,越负越弱。Zigbee 官方建议阈值 -70 dBm,但实测:

RSSI 区间 表现
> -50 dBm 完美,路由稳定
-50 到 -65 dBm 良好
-65 到 -75 dBm 可用,偶尔丢包
-75 到 -80 dBm 危险,10 分钟丢 1-2 次包
< -80 dBm 严重危险,随时掉线

我 87 台设备里的 RSSI 分布:

RSSI > -60 dBm:  31 台
RSSI -60 ~ -70:  42 台
RSSI -70 ~ -75:  11 台  ⚠ 已加路由器缓冲
RSSI < -75:       3 台  ❌ 已搬迁 / 待搬迁

这里要坦承一个不完全确定的点:Zigbee 里 RSSI 和 LQI 的关系并不是线性的。我看到过 RSSI -72 dBm 但 LQI 200 的设备,也看到过 RSSI -55 dBm 但 LQI 80 的设备。LQI 更能反映"这条链路能不能稳定路由",但 Zigbee 协议栈里 RSSI 是硬件层直接给出的,更客观。实操建议:LQI 优先,RSSI 兜底。

3. 5 类拓扑优化点:从路由器放置到 OTA 顺序

拓扑优化不是"买个更好的路由器",是把已有设备的位置、角色、参数调到最优。以下 5 个优化点,是我这一年踩了 4 个坑之后总结出来的顺序。

3.1 优化点一:路由器放置——把 Mesh 变成星型加环

原则:Zigbee Mesh 理想拓扑是协调器为中心的一棵多叉树,但实际会退化。放路由器时按下面原则:

  1. 协调器周围 1.5 米内不放路由器。这个距离路由器信号反而更差(近场干扰),路由器应该放在离协调器 2-5 米的距离。
  2. 每个路由器只服务 3-6 台终端。87 台设备里我 21 个路由器,平均每台路由器带 3-4 台终端。超过 6 台,路由器转发压力过大,LQI 会掉。
  3. 关键位置放冗余路由器。走廊、卫生间这种容易形成信号死角的地方,放两台以上路由器形成"环"——单台路由器挂了另一台接管。
  4. 避开金属和大功率设备。路由器不要放在:微波炉旁、鱼缸旁边、金属书架深处、电视背面(后面是 WiFi 路由器)、弱电箱(金属屏蔽)。

我踩过的坑:一开始把客厅的智能插座堆在电视柜上,路由器信号被 WiFi 路由器"打穿",LQI 从 200 掉到 90。搬开 1 米立刻恢复。

决策规则:

位置 = 距离协调器 2-5 米
     AND 离任何 WiFi 路由器 > 1 米
     AND 不在金属屏蔽环境
     AND 不遮挡(避免放在家具内部深处)

3.2 优化点二:RSSI 阈值——分区设置而不是全局

默认误区:把 Zigbee 全局 RSSI 阈值设成 -70 dBm 之类的固定值。真实情况是:客厅信号好,同一阈值在卫生间可能是"信号良好",在阳台可能是"信号危险"。

我的做法:在 z2m 里给每个房间的设备手动标 RSSI 告警阈值:

# /data/configuration.yaml
devices:
  '0x00158d0001a2b3c4':  # 客厅温湿度
    friendly_name: '客厅-温湿度'
    # 客厅信号好,阈值放宽
    rssi_threshold: -75
  '0x00158d0001b2c4d5':  # 卫生间温湿
    friendly_name: '卫生间-温湿'
    # 卫生间信号差,阈值收紧
    rssi_threshold: -65

注意:z2m 的 rssi_threshold 不是原生字段,是我在设备自定义扩展里加的告警逻辑。核心思路是——每 6 小时抓一次所有设备 RSSI,比对阈值,触发告警。这段逻辑放在 HA 自动化里:

alias: Zigbee RSSI Threshold Check
trigger:
  - trigger: time_pattern
    minutes: '/60'   # 每小时检查一次
condition: []
action:
  - service: shell_command.zigbee_rssi_report
    response_variable: rssi_report
  - service: script.check_zigbee_rssi_alerts
    data:
      report: '{{ rssi_report.response_json }}'
mode: single
max_exceeded: silent

这个 shell_command 就是拉一次 z2m API:

#!/bin/bash
# /usr/local/bin/zigbee_rssi_report.sh
curl -s http://localhost:8081/api/v1/networkmap \
  | jq '.networkmap[0].nodes | map(select(.type != "Coordinator")) |
        map({ieee: .ieeeAddr, name: .friendlyName, rssi: (.neighbors[0].lqi // null), lqi: (.neighbors[0].rssi // null)})' \
  | jq -c '.'

关键点:RSSI 阈值不是用来"卡"网络的,是拿来提前 3-7 天发现链路衰减。当某台设备的 RSSI 从 -60 慢慢降到 -72,你还能干预(挪路由器、加中继),等到它降到 -80 掉线已经晚了。

3.3 优化点三:路由跳数——3 跳是天花板,2 跳是舒适区

Zigbee 协议允许 8 跳,但实测超过 3 跳,包丢失率开始显著上升。原因:

  • 每跳引入 5-15ms 延迟,8 跳累计 40-120ms,用户能感知
  • 每跳有 0.5-1% 丢包率,8 跳累计丢包率能到 8%
  • 路由跳数越多,链路越依赖"每台路由器都稳定",一台挂整个链断

决策规则:

跳数 建议动作
1 跳 优秀,无需处理
2 跳 舒适,无需处理
3 跳 关注,若 RSSI 也低于 -70,考虑加路由器
4 跳 加路由器,把这台设备拉回 2 跳
5+ 跳 必须干预,加路由器或直接搬迁设备

我踩过的坑:阳台尽头一个门窗传感器,一开始是 5 跳。我以为是设备问题,反复重启协调器、换频道都没用。最后发现——阳台到协调器中间的厨房和玄关只有 3 台路由器,全部挤在走廊一侧,形成"信号长走廊"。解决方案:在阳台装个智能插座当路由器,跳数从 5 直接变 2。

怎么查每台设备的跳数:

# 通过 z2m API 拉拓扑,计算跳数
curl -s http://localhost:8081/api/v1/networkmap | jq '
  .networkmap[0].nodes | map({
    ieee: .ieeeAddr,
    parent: .parent,
    neighbors: (.neighbors | length)
  })'

parent 字段是父节点。递归计算到协调器的路径长度就是跳数。我用 HA 里的这个脚本每小时更新一次:

# /config/scripts.yaml
update_zigbee_hops:
  sequence:
    - alias: Get network map
      service: shell_command.zigbee_networkmap
      response_variable: netmap
    - alias: Calculate hops
      service: shell_command.zigbee_calc_hops
      data:
        netmap: '{{ netmap.response_json }}'
      response_variable: hops
    - alias: Update sensors
      repeat:
        for_each: '{{ hops.response_json }}'
        sequence:
          - service: shell_command.zigbee_update_hop
            data:
              ieee: '{{ repeat.item.ieee }}'
              hops: '{{ repeat.item.hops }}'
  mode: single

注意:这套 shell 脚本是我从 z2m 网络图 JSON 里解析出来的,如果 z2m 版本更新了 JSON 结构,脚本要跟着改。目前 z2m 1.40 版本是稳定的。

3.4 优化点四:信道规划——避开 WiFi 是最省事的一次优化

先记住一张表:

Zigbee 信道 与 WiFi 冲突 与微波炉冲突 家用推荐
11 WiFi Ch1 严重 是 ❌
12 WiFi Ch1 中度 是 ❌
13 WiFi Ch1 中度 是 ❌
14 无 否 ✅ 首选
15 无 否 ✅ 首选
16 WiFi Ch6 中度 否 ⚠
17 WiFi Ch6 中度 否 ⚠
18 WiFi Ch6 轻度 否 ⚠
19 无 否 ✅ 可用
20 无 否 ✅ 家用首选
21 无 否 ✅ 可用
22 WiFi Ch11 轻度 否 ⚠
23 WiFi Ch11 中度 否 ⚠
24 WiFi Ch11 中度 否 ⚠
25 无 否 ✅ 家用首选
26 无 否 ✅ 可用

扫描命令(在 z2m 容器里,或者用 HA shell command 触发):

# 扫描 11-26 信道的 RSSI,越负越干净
for ch in 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26; do
  rssi=$(curl -s "http://localhost:8081/api/v1/channel?chan=$ch" | jq '.rssi')
  echo "Ch $ch: $rssi dBm"
done

决策规则:

  1. 家用首选 15、20、25——三条干净信道
  2. WiFi 用 Ch1 → Zigbee 用 15/20/25
  3. WiFi 用 Ch6 → Zigbee 用 14/19/25
  4. WiFi 用 Ch11 → Zigbee 用 15/20
  5. 同时用多个 WiFi 信道 → Zigbee 挑 RSSI 最负的

为什么信道优化这么重要:一次踩坑——邻居换了 WiFi6 路由器,信道 6 打满,我家 Zigbee 信道 20 掉线率从 0.1% 变成 3%。切到信道 25,2 分钟内恢复。这一步是最快见效的优化,通常 5 分钟内能看到效果。

3.5 优化点五:OTA 升级顺序——分批、有序、避开高峰

OTA 是拓扑优化的隐藏杀手。一次批量 OTA 能让协调器 CPU 打满 5 分钟,然后所有设备离线 8 分钟。我的策略:

原则 1:分批升级,每批 3-5 台

# 我在 HA 里做了一个"OTA 队列"脚本
trigger:
  - platform: manual
variables:
  batch_size: 3
sequence:
  - service: input_select.select_option
    target:
      entity_id: input_select.zigbee_ota_pause
    data:
      option: 'pause'   # 暂停告警
  - repeat:
      count: 3
      sequence:
        - service: shell_command.zigbee_ota_batch
          data:
            count: '{{ batch_size }}'
        - service: wait_for_trigger
          trigger:
            - platform: shell_command
              command: 'sleep 30 && echo done'
        - service: shell_command.zigbee_check_online
          response_variable: online_check
  - service: input_select.select_option
    target:
      entity_id: input_select.zigbee_ota_pause
    data:
      option: 'resume'
mode: single

原则 2:路由器和终端分开升级

先升级路由器(离线影响小),再升级终端(离线影响大)。原因是路由器升级时会短暂"下线",它的子树需要时间切换到新父节点;如果同时批量升级路由器,会形成大面积的路由重新选举,协调器负载爆炸。

升级顺序:
1. 优先升级智能插座(离线不影响业务)
2. 再升级网关类(离线影响网关内其他设备,但网关数量少)
3. 再升级智能灯泡(离线只影响灯本身)
4. 最后升级传感器(最晚,因为传感器离线时 HA 可能触发误告警)

原则 3:避开高峰时段

Zigbee Mesh 的负载在早晨 6-9 点、晚上 19-22 点最高(智能家居大量自动化集中触发)。OTA 选在凌晨 2-4 点,Mesh 空闲,升级失败率最低。

原则 4:升级前 silence 告警

# HA 自动化:升级前 30 分钟 silence Zigbee 离线告警
alias: Silence Zigbee Offline Alert Before OTA
trigger:
  - platform: state
    entity_id: input_boolean.zigbee_ota_in_progress
    to: 'on'
condition: []
action:
  - service: input_boolean.turn_on
    target:
      entity_id: input_boolean.zigbee_alert_silenced
  - service: timer.cancel
    target:
      entity_id: timer.zigbee_alert_silence
  - service: timer.start
    target:
      entity_id: timer.zigbee_alert_silence
    data:
      minutes: 90

关于 OTA 升级顺序:这里有个我不完全确定的点——z2m 官方文档没明确推荐升级顺序,我上面的策略是根据我自己的翻车经验总结的。有可能某些设备(比如新款米家网关)升级顺序不影响,或者有些设备的升级窗口本来就很短,分批和不分批差别不大。保守做法是分批 + 避高峰 + silence 告警,这三步做下来,翻车概率会低很多。

4. Zigbee2MQTT 拓扑可视化:graph view 实操

拓扑优化最大的敌人是"看不见"。z2m 自带 Graph View,配合 HA 的 Lovelace dashboard 可以做到实时监控。

4.1 打开 Graph View

z2m 前端(默认 http://<z2m-host>:8080)左侧菜单 → Topologie(或 Graph View)。第一次打开时会拉一次完整拓扑图。

你需要能看到的:

  • 所有设备节点,按类型着色(协调器红、路由器蓝、终端绿)
  • 节点之间的连线,粗细代表 LQI 强度
  • 每个节点上的数字:邻居数、RSSI、电量
  • 跳数:从协调器出发的最短路径

4.2 把拓扑图拉到 HA dashboard

z2m 的 graph view 是 SVG,可以直接嵌入 HA:

# HA dashboard
- type: custom:svg-images
  svg:
    - type: custom:svg-graph-view
      url: http://z2m-host:8080/api/v1/networkmap
      # 或者直接用 iframe
- type: iframe
  url: http://z2m-host:8080/Topologie
  aspect_ratio: 100%

注意:z2m 面板有密码保护,iframe 嵌入可能被阻止。我在 HA 前面加了一层 Nginx reverse proxy,统一处理认证。

4.3 关键指标看板

我在 HA 里做了一个 Zigbee 网络健康看板,5 个卡片:

title: Zigbee Mesh Health
cards:
  - type: glance
    entities:
      - sensor.zigbee_total_devices    # 设备总数
      - sensor.zigbee_offline_count    # 离线数
      - sensor.zigbee_avg_lqi          # 平均 LQI
      - sensor.zigbee_max_hops         # 最大跳数
      - sensor.zigbee_coordinator_load # 协调器负载 %
      - sensor.zigbee_channel_rssi     # 当前信道 RSSI
  - type: history-graph
    title: 24h LQI Trend
    entities:
      - entity: sensor.zigbee_avg_lqi
        name: 平均 LQI
      - entity: sensor.zigbee_offline_count
        name: 离线数
  - type: statistics
    stat: sensor.zigbee_avg_lqi
    days_to_show: 7

这些 sensor 是通过 z2m 的 MQTT 事件 + 定时脚本聚合出来的。核心采集脚本:

#!/bin/bash
# /usr/local/bin/zigbee_health_snapshot.sh
NETMAP=$(curl -s http://localhost:8081/api/v1/networkmap)
TOTAL=$(echo "$NETMAP" | jq '.networkmap[0].nodes | length - 1')
OFFLINE=$(echo "$NETMAP" | jq '[.networkmap[0].nodes[] | select(.lastSeen != null and (.lastSeen < (.now - 60000)))] | length')
AVG_LQI=$(echo "$NETMAP" | jq '[.networkmap[0].nodes[].neighbors[].lqi] | add / length')
MAX_HOPS=$(echo "$NETMAP" | jq '[.networkmap[0].nodes[] | .hops // 0] | max')

# 通过 MQTT 发布
mosquitto_pub -h localhost -p 1883 -u hio -P password -t 'zigbee/health/total' -m "$TOTAL"
mosquitto_pub -h localhost -p 1883 -u hio -P password -t 'zigbee/health/offline' -m "$OFFLINE"
mosquitto_pub -h localhost -p 1883 -u hio -P password -t 'zigbee/health/avg_lqi' -m "$AVG_LQI"
mosquitto_pub -h localhost -p 1883 -u hio -P password -t 'zigbee/health/max_hops' -m "$MAX_HOPS"

每 5 分钟跑一次,sensor.zigbee_* 就都更新了。

4.4 用 graph view 定位问题设备

方法一:找"孤岛"

如果 graph view 里出现一个节点只有一条连线(父节点),且这条线颜色是黄色或红色(LQI < 120),说明这台设备是"孤岛",风险极高。要么搬它,要么在附近加路由器。

方法二:找"链路瓶颈"

如果两个路由器之间是全网唯一连接(比如"从协调器到某个房间只经过一条路径"),那这条链断了整个房间就离线。检查方法:在 graph view 里手动把某个节点"藏"起来,看它下面的设备是否还能连到协调器。

方法三:找"路径绕远"

正常拓扑里,某台设备的父节点是离它最近的邻居。如果 graph view 里看到某台设备的父节点是它的"远方"邻居(比如物理上隔了 2 米,但父节点是 5 米外的),说明路由表被 z2m 或协调器算错了。解决:重启该设备让它重新路由选举,或者临时把中间的路由器拔电再插上(触发邻居表重建)。

5. 4 个真实事故复盘

这一章是我这一年里最痛的 4 次事故,复盘的时候希望能帮你避开。

5.1 事故一:87 台设备凌晨 3 点集体掉线

时间:2025 年 3 月,深夜 现象:3 分钟内 60+ 台设备离线,HA 告警刷屏 40 条 根因排查:

  1. 检查协调器:日志显示 Coordinator heartbeat failed 持续 20 秒
  2. 检查串口:dmesg | tail 显示 SLZB-06P 有 USB reset 事件
  3. 检查协调器位置:放在客厅电视柜上,紧邻 WiFi 路由器
  4. 检查环境温度:空调关着,室温 26°C,正常

根因:WiFi 路由器晚上自动切换到 5G + 2.4G 混合模式,发射功率增加,SLZB-06P 距离太近,Zigbee 芯片被 2.4G 强信号干扰,USB 复位。

修复:

  1. 把 SLZB-06P 搬到离路由器 1.5 米外的地方
  2. 在路由器设置里把 2.4G 信道固定到 Ch6(避开 Zigbee 20 信道)
  3. 给 SLZB-06P 加个金属外壳的屏蔽盒(其实没必要,但心里踏实)

教训:协调器位置比型号更重要。同一台 SLZB-06P,位置对了跑 6 个月无掉线,位置不对每周掉线 2-3 次。

5.2 事故二:某个路由跳 2 变成 4,5 台终端集体掉线

时间:2025 年 5 月 现象:卧室所有设备(18 台)突然显示 4 跳,其中 5 台电池传感器离线 根因排查:

  1. 打开 graph view,看到卧室的路由器 A 从协调器直连变成 4 跳
  2. 追路由表发现:客厅的路由器 B 挂了(拔掉了)
  3. 路由 B 是主卧到协调器的唯一"桥"

根因:主卧的路由器 A 和客厅的路由器 B 之间有唯一连接,B 拔了,A 找不到新的父节点,被 z2m 拉到了远处。

修复:

  1. 加了一条从主卧到阳台的路由(阳台到客厅有另一条路由)
  2. 现在主卧有三条到协调器的路径,任何一条断了都能自愈

教训:冗余不是奢侈。关键位置(比如主卧、卫生间)至少放两条独立路由路径。87 台设备里我最怕的就是"某台路由器拔了,一个房间离线"这种单点故障。

5.3 事故三:一次 OTA 让协调器 CPU 打满 8 分钟

时间:2025 年 6 月 现象:批量触发 15 台设备 OTA,3 分钟后协调器 CPU 100%,8 分钟后所有设备离线 5 分钟 根因排查:

  1. 协调器负载监控显示:CPU 89% → 96% → 100%(阶梯式上升)
  2. 每台设备 OTA 大约 60-90 秒,15 台并发 = 协调器同时处理 15 个 OTA 会话
  3. SLZB-06P 是 ARM Cortex-M3 100MHz,处理能力有限

根因:并发 OTA 太多。z2m 默认不做 OTA 排队,15 台一起升等于让协调器同时处理 15 个高负载任务。

修复:

  1. 强制开启 ota.update_by_hand: true,禁用自动 OTA
  2. 用 HA 脚本分批,每批 3 台,间隔 5 分钟
  3. 升级前 silence 告警 30 分钟
  4. 升级时机固定在凌晨 3 点(Mesh 空闲)
# OTA 分批脚本
#!/bin/bash
# /usr/local/bin/zigbee_ota_batch.sh
DEVICES=$(curl -s http://localhost:8081/api/v1/ota/available | jq -r '.devices[]')
COUNT=0
for dev in $DEVICES; do
  echo "OTA: $dev"
  curl -s -X POST http://localhost:8081/api/v1/devices/$dev/ota/update
  COUNT=$((COUNT+1))
  if [ $COUNT -ge 3 ]; then
    echo "Batch done, waiting 5 min..."
    sleep 300
    COUNT=0
  fi
done

教训:OTA 是拓扑的杀手,一定要分批、避高峰、silence 告警。

5.4 事故四:Zigbee 1.2 老网关里 6 个 Hue 灯泡迁移失败

时间:2025 年 7 月 现象:想把旧 Aqara M2 网关里的 6 个早期 Hue 灯泡迁到 Z2M 网络,配对时 4 个成功 2 个失败,失败的两个报 Error joining network: Error 0x25 根因排查:

  1. 两个失败的灯泡是 2014 年前生产的 Hue A19
  2. 其他 4 个是 2016 年后的 Hue 灯泡,都成功
  3. 老灯泡固件版本是 1673,新灯泡是 2125+
  4. 查 Philips Hue 官方文档:固件 1673 之前不支持 Zigbee 3.0

根因:老 Hue 灯泡不支持 Zigbee 3.0,无法迁移。

修复:

  1. 尝试用旧 Hue Bridge 触发 OTA(如果家里有旧 bridge),失败——固件更新后依然不支持 3.0
  2. 联系 Philips 官方支持,得到回复"这两台需要硬件替换"
  3. 最终弃用这两台,换了新的 Tuya 白灯泡

教训:Zigbee 1.2 到 3.0 不是所有设备都能平滑迁移。买旧设备做 Zigbee 3.0 网络时要确认:① 固件支持 Zigbee 3.0(或者能通过 OTA 升到支持 3.0 的版本);② 设备厂商还在维护;③ 不是最老的固件。这一条我踩过坑才懂,写下来的时候希望能帮你省一次。

6. 从 Zigbee 1.2 到 3.0 的迁移坑

Zigbee 3.0 是 2017 年发布的规范,但生态迁移用了 5 年才基本完成。今天买的新设备 90% 都是 3.0,但家里翻出来的老设备可能是 1.2 / ZLL。

6.1 Zigbee 1.2、ZLL、Zigbee 3.0 差异速览

特性 Zigbee 1.2 (2012) ZLL (2013) Zigbee 3.0 (2017)
网络类型 无 Mesh 或简单 Mesh 只支持光照明 完整 Mesh
加密 弱 无 AES-128
路由器角色 有限 无 完整
跨品牌兼容 差 只兼容光照明 好
电池设备支持 有限 有限 好

要点:Zigbee 1.2 和 ZLL 不能互操作,也不能直接加入 Zigbee 3.0 网络。老设备要迁到 3.0,必须先升级到支持 3.0 的固件。

6.2 迁移流程:4 步

Step 1:识别老设备

在旧网关(Aqara M2、Hue Bridge 1 代等)里查看设备列表。判断每个设备的协议:

  • Aqara 2018 年前产品:多为 Zigbee 1.2
  • Hue 2017 年前产品:多为 ZLL
  • 2018 年后产品:基本是 3.0

Step 2:尝试 OTA 升级固件

在旧网关的 App 里触发"升级网络"或"升级设备固件"。这一步可能耗时 30-90 分钟,不要中途断电。

Step 3:从旧网关移除设备

升级完成后,在旧网关里逐个"忘记设备"。不要在设备端直接配对新网络——旧网络的密钥还在设备里,会冲突。

Step 4:从新网关(Z2M)重新配对

正常配对流程。注意:Z2M 里如果设备固件不支持 3.0,配对会失败并报 Error joining network: Error 0x25 之类的错误。这时只能弃用或换设备。

6.3 迁移中最容易踩的 3 个坑

坑一:以为固件升级是可选的

Zigbee 3.0 是网络层协议,不是应用层。设备不升级到 3.0,就永远无法加入 3.0 网络,只能保持在自己原来的 1.2 网络里。固件升级是必要步骤,不是可选项。

坑二:老 Hue 灯泡升级后依然不支持 3.0

Philips Hue 2014 年前的 A19 灯泡,固件升级后仍然是 ZLL,不支持 3.0。这类设备必须硬件替换。检查方法:在 Hue App 里看设备"固件版本",低于 1700 的多数不支持 3.0。

坑三:迁移过程中旧网关还开着

旧网关和新网关同时开着,且在同一 WiFi 频段,会互相干扰。迁移时先把旧网关断电。

6.4 完全不支持 3.0 的设备怎么办

有些设备(如 2014 年前的一些 Hue 灯泡、早期 Philips 传感器)完全不支持 Zigbee 3.0,无法迁移。三个选项:

  1. 弃用:最省事,换新设备
  2. 保留旧网关:把旧网关里的这些老设备单独维护,通过 HomeKit / WiFi / 旧网关 API 接入 HA
  3. 等厂商 OTA:某些厂商(比如 Sonoff)会不定期发布固件,尝试触发 OTA 后重试配对

7. 生产 checklist:从 20 台到 100 台的 8 步

这一节是照着做就能上线的清单。按顺序执行,每步完成打勾。

Step 1:确认协调器型号和位置

  • 协调器型号(推荐 SLZB-06P)
  • 位置:距离 WiFi 路由器 > 1 米
  • 位置:不遮挡(避免放在弱电箱、金属家具深处)
  • 位置:家中中心区域
  • USB 接口远离强电设备(避免电源适配器干扰)

Step 2:分类所有设备的角色

  • 列出现网所有设备
  • 每台设备标注:Coordinator / Router / EndDevice
  • 电池设备 100% 是 EndDevice
  • Router 设备 ≥ 终端设备的 1/8

Step 3:修正错误的角色标注

# 拉出所有设备的当前类型
curl -s http://localhost:8081/api/v1/networkmap | \
  jq '.networkmap[0].nodes[] | {name: .friendlyName, type: .type}'
  • 发现 Router 但实际是电池设备?改 powered_by_battery: true
  • 发现 EndDevice 但实际是插电设备?改 powered_by_battery: false
  • 修改 configuration.yaml 后重启 z2m

Step 4:扫描并选定信道

# 扫描 RSSI
for ch in 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26; do
  rssi=$(curl -s "http://localhost:8081/api/v1/channel?chan=$ch" | jq '.rssi')
  echo "Ch $ch: $rssi dBm"
done
  • 找 RSSI 最低的 3 个信道
  • 家用首选 15/20/25
  • 修改 configuration.yaml:network_parameters.channel: <选中值>
  • 重启 z2m,观察 24 小时掉线率

Step 5:调整路由器位置

  • 每个房间至少 1 个路由器
  • 关键房间(主卧、卫生间)至少 2 个路由器(冗余)
  • 走廊 / 长距离区域放路由器形成"接力"
  • 路由器距离协调器 2-5 米
  • 路由器离 WiFi 路由器 > 1 米
  • 路由器不在金属屏蔽环境

Step 6:设置分区 RSSI 阈值

  • 客厅:-75 dBm
  • 卧室:-70 dBm
  • 卫生间:-65 dBm(信号最差的房间)
  • 阳台:-70 dBm
  • 部署每小时 RSSI 检查脚本

Step 7:设置跳数监控

  • 部署每小时跳数扫描脚本
  • 告警阈值:跳数 ≥ 4
  • 告警方式:Telegram / HA 通知
  • 每月人工 review 一次跳数分布

Step 8:部署 OTA 分批流程

  • 开启 ota.update_by_hand: true
  • 部署 OTA 分批脚本(每批 3 台)
  • 设置凌晨 2-4 点自动触发(可选)
  • 部署告警 silence 自动化
  • 每月检查一次待升级设备

8. 速查 Cheat Sheet

8.1 关键阈值速查

指标 良好 可用 危险
LQI ≥ 180 120-180 < 80
RSSI (dBm) > -60 -60 ~ -70 < -75
路由跳数 ≤ 2 3 ≥ 4
单路由器下挂终端数 ≤ 4 5-6 ≥ 7
协调器直连设备数 ≤ 15 15-25 ≥ 30
路由器:终端比 1:5 1:6-8 ≥ 1:10

8.2 z2m 诊断命令速查

# 查看网络拓扑
curl -s http://localhost:8081/api/v1/networkmap

# 查看某设备的邻居 LQI
zigbee2mqtt_lqi 0x00158d0001a2b3c4

# 查看某设备的元数据(含父节点、跳数)
zigbee2mqtt_get_metadata 0x00158d0001a2b3c4

# 扫描信道 RSSI
curl -s http://localhost:8081/api/v1/channel?chan=25

# 允许设备加入(临时)
zigbee2mqtt_permit_join -t 300   # 300 秒后自动关闭

# 触发单设备 OTA
curl -X POST http://localhost:8081/api/v1/devices/0x00158d0001a2b3c4/ota/update

# 查看待升级设备
curl -s http://localhost:8081/api/v1/ota/available

8.3 拓扑故障决策树

设备离线
├─ 单台离线
│  ├─ LQI < 80 → 挪位置 / 加路由器
│  ├─ LQI 正常 → 检查设备电量(电池设备)
│  ├─ 跳数 ≥ 4 → 加路由器
│  └─ 以上都正常 → 重启设备
├─ 一台路由器挂了
│  ├─ 有冗余路径 → 自愈,观察
│  └─ 无冗余路径 → 该房间所有设备离线,需干预
└─ 批量离线(>5 台)
   ├─ 协调器 CPU > 90% → 检查是否并发 OTA
   ├─ 协调器 CPU 正常 → 检查信道 RSSI 是否突变
   ├─ 信道变化 → 邻居 WiFi 干扰,切信道
   └─ 以上都正常 → 检查协调器位置和温度

8.4 configuration.yaml 关键字段

advanced:
  last_seen: ISO_8601        # 必须开,HA 才知设备真实状态
  availability: true         # 必须开
  availability_timeout: 60   # 60 秒无心跳判离线
  cache_state: true          # 重连后恢复最后状态

ota:
  update_by_hand: true       # 生产环境必须 true
  check_update_interval: 168 # 7 天检查一次

network_parameters:
  channel: 25                # 根据扫描结果选,家用 15/20/25
  pan_id: null               # null 随机,避免撞 PAN

devices:
  '0x00158d0001a2b3c4':
    friendly_name: '客厅温湿度'
    powered_by_battery: true # 强制角色标注

8.5 一句话决策

  • 设备掉线 → 先看 LQI 再看跳数
  • 批量掉线 → 先看协调器负载再看信道
  • 路由抖动 → 检查拓扑是否有冗余
  • OTA 掉线 → 分批 + silence 告警
  • 老设备迁移失败 → 固件不支持 3.0,只能换设备

9. FAQ

Q1:为什么不能直接买更贵的协调器解决所有问题?

协调器型号决定上限,但决定下限的是拓扑。同一台 SLZB-06P,位置不对每周掉 3 次,位置对了 6 个月无掉线。协调器只是网络的心脏,心脏好不代表血管通——血管(拓扑)不通,心脏再强也救不了。

Q2:家里 40 台设备,还需要做拓扑优化吗?

需要。我 20 台设备时就遇到过路由跳数不合理的问题,只是不明显。从 20 台开始就应该建立基线:知道每台设备的跳数、LQI、RSSI 分布,等到 40 台扩到 60 台时能对比。

Q3:电池传感器能强制标记为 Router 提高路由覆盖吗?

不能。Zigbee 协议里 Router 需要持续在线,电池设备即使被强制标为 Router 也会因为休眠而无法履行路由职责,反而拖慢拓扑稳定性。想让某个位置有路由器,就得装个插电设备(智能插座最便宜,30-50 元)。

Q4:协调器能放在弱电箱里吗?

不建议。弱电箱是金属环境,Zigbee 2.4G 信号会被金属屏蔽,协调器辐射范围可能只有 2-3 米。必须放在开阔、无金属屏蔽的位置,最好距离 WiFi 路由器 1-2 米。

Q5:一个智能插座能带多少台设备?

经验值:4-6 台终端 + 0-2 个子路由器(子路由器 = 挂在它下面的其他路由器)。超过 6 台后延迟会肉眼可见上升,超过 8 台开始出现丢包。智能插座不是无限容量的,它的转发能力受芯片性能限制。

Q6:如何判断 z2m 版本是否需要升级?

判断条件:① 有新的设备型号要配对(老版本可能没 definition);② 出现了已知的 bug(比如某设备频繁离线,新版本修了);③ 你用了新功能(比如 OTA 队列)。升级前先看 changelog,重点看是否有 breaking change 影响你的配置。

Q7:Zigbee Mesh 和 WiFi Mesh 有什么区别?

拓扑相同,物理层不同:Zigbee 用 2.4G 但协议简单、功耗低;WiFi Mesh 用 802.11 但复杂、功耗高。关键区别:Zigbee Mesh 里终端设备可以休眠,WiFi Mesh 里每个节点必须常在线。这是 Zigbee 更适合智能家居传感器场景的根本原因。

Q8:如果我的 Zigbee 设备在 z2m 面板里显示"离线"但 HA 里显示"在线"?

这是 advanced.last_seen 没开导致的。必须开启 last_seen: ISO_8601,让 HA 通过 last_seen 属性感知设备真实状态。不开的话,z2m 会保持设备为"在线"直到重启,HA 也就显示"在线",但实际可能已经离线几小时了。

Q9:Zigbee 2.4G 和 Thread 未来会不会合并?

不会合并,但会共存。Thread 定位是替代 Zigbee,用于 Matter 生态,但 Thread 需要额外边界路由设备(如 Apple HomePod、三星 SmartThings Hub),部署成本更高。Zigbee 生态设备更丰富,短期内不会被替代。中长期策略:新设备可以买 Thread,老设备保持 Zigbee,两条网络并行。

Q10:有没有工具能自动优化拓扑?

目前没有成熟的自动优化工具。z2m 有"网络地图质量评分"(zigbee2mqtt_networkmap 里每个节点的 quality 字段),可以帮你定位问题设备,但不会自动搬设备或调信道。"半自动"是当前最优解:脚本采集指标 + 你根据决策规则手动干预。

10. 一些不完全确定的观察

这篇大部分内容是我这一年实测得出的,但有几个点我目前还没有完全确定的答案,写下来是为了后续观察:

观察 1:Zigbee 2.4G 的干扰是否被 WiFi 6 加剧?

邻居换了 WiFi 6 路由器后,我家 Zigbee 掉线率确实上升了 3-5 倍。但不确定是 WiFi 6 本身的干扰,还是邻居的信号变强了导致 RSSI 曲线更陡。这个需要更多样本才能定论。

观察 2:Zigbee Pro 和 Zigbee 3.0 在实际使用中的差异。

理论上 Zigbee Pro 支持更复杂的 Mesh 和路由,但在智能家居场景里几乎没差别。如果设备贵 30% 以上就选 Zigbee Pro,否则 Zigbee 3.0 够用。这是我的判断,不是官方结论。

观察 3:87 台设备是否已经是 SLZB-06P 的上限?

目前 SLZB-06P 在我家稳定跑了 6 个月,没遇到负载上限问题。z2m 官方文档说 SLZB-06P 支持 200+ 设备,但那是理论值。实际家用的话,超过 150 台可能要考虑换 CC2652P 或双协调器架构——这是推测,我还没到那一步。

观察 4:Matter over Thread 是否会终结 Zigbee?

Matter 是 2022 年推出的,理论上可以覆盖 Zigbee 的所有场景。但 Matter 的落地速度比预期慢,2025 年的 Matter 生态远没有 Zigbee 丰富。我预计 Zigbee 至少还能活 5 年,但新买的设备开始支持 Matter + Zigbee 双协议会是常态。

11. 关联阅读