|
蓝牙 Mesh 组网指南:智能家居多设备联动

蓝牙 Mesh 组网指南:智能家居多设备联动

为什么选择蓝牙 Mesh?

说到智能家居组网,很多人第一反应是 WiFi 或者 Zigbee。但蓝牙 Mesh 其实是个被低估的选手。

它有几个明显优势:

  • 覆盖范围广:通过节点中继,信号可以穿墙过户,轻松覆盖 100+ 平米

  • 低功耗:设备待机功耗极低,电池供电的传感器能用几个月甚至一年

  • 成本低:蓝牙芯片价格亲民,ESP32 开发板几十块就能搞定

  • 手机直连:不需要额外网关,手机就能直接控制设备

如果你正在搭建智能家居系统,又不想花大价钱买成套方案,蓝牙 Mesh 是个很务实的选择。

硬件清单

设备型号数量单价总价
ESP32 开发板ESP32-WROOM-323 块¥25¥75
蓝牙 Mesh 模组JDY-642 个¥18¥36
温湿度传感器SHT301 个¥15¥15
继电器模块5V 单路2 个¥8¥16
USB 数据线Micro-USB3 根¥5¥15
面包板830 孔1 个¥12¥12
杜邦线公对公/母对母20 根¥8¥8
合计---¥177

所有配件都能在淘宝或立创商城买到,总价不到 200 块就能搭建一套完整的蓝牙 Mesh 网络。

蓝牙 Mesh 基础概念

在动手之前,先搞懂几个核心概念:

节点(Node)

每个加入 Mesh 网络的设备都是一个节点。节点可以:

  • 发送消息(比如温湿度传感器上报数据)

  • 接收消息(比如继电器接收开关指令)

  • 中继消息(帮助其他节点转发信号)

元素(Element)

一个节点可以包含多个元素。比如一个智能开关面板,可能有 3 个按键,每个按键就是一个独立的元素。

模型(Model)

模型定义了设备的功能。常见的有:

  • Generic OnOff:开关控制

  • Sensor:传感器数据上报

  • Light Lightness:灯光亮度调节

发布/订阅(Publish/Subscribe)

这是 Mesh 网络的核心机制:

  • 发布地址:设备向哪个地址发送消息

  • 订阅地址:设备监听哪个地址的消息

举个例子:温湿度传感器发布到地址 0xC001,继电器订阅 0xC001,这样传感器数据就能自动触发继电器动作。

环境搭建

1. 安装 ESP-IDF

ESP32 的官方开发框架是 ESP-IDF。我们用 Docker 方式安装,避免污染系统环境:

# 拉取 ESP-IDF Docker 镜像
docker pull espressif/idf

# 创建工作目录
mkdir -p ~/esp-mesh-project
cd ~/esp-mesh-project

# 启动容器
docker run --rm -v $PWD:/project -w /project -it espressif/idf bash

2. 创建项目

# 在容器内执行
idf.py create-project bluetooth-mesh-node
cd bluetooth-mesh-node

3. 配置 Mesh 参数

编辑 sdkconfig,启用蓝牙 Mesh 支持:

idf.py menuconfig

导航到:

Component config → Bluetooth → Bluedroid Enable → Enable Bluetooth Mesh

保存退出后,执行:

idf.py fullclean

代码实现

节点主程序

创建 main/main.c

#include 
#include "esp_log.h"
#include "nvs_flash.h"
#include "esp_bt.h"
#include "esp_ble_mesh_api.h"

static const char *TAG = "MESH_NODE";

// 设备 UUID(每个设备需要唯一)
static uint8_t dev_uuid[16] = {
    0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd,
    0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd
};

// Mesh 配置
static esp_ble_mesh_cfg_srv_t config_server = {
    .relay = ESP_BLE_MESH_RELAY_DISABLED,
    .beacon = ESP_BLE_MESH_BEACON_ENABLED,
};

static esp_ble_mesh_model_t models[] = {
    ESP_BLE_MESH_MODEL_CFG_SRV(&config_server),
    ESP_BLE_MESH_MODEL_NONE(),
};

static esp_ble_mesh_elem_t elements[] = {
    ESP_BLE_MESH_ELEMENT(0, models, models, models),
};

static esp_ble_mesh_comp_t composition = {
    .cid = 0x02E5,
    .elements = elements,
    .element_count = ARRAY_SIZE(elements),
};

static esp_ble_mesh_prov_t provision = {
    .uuid = dev_uuid,
    .uuid_size = sizeof(dev_uuid),
    .attention_duration = 3,
};

// Mesh 事件回调
static void mesh_callback(esp_ble_mesh_event_t *event) {
    switch (event->event) {
        case ESP_BLE_MESH_PROVISION_NODE_EVT:
            ESP_LOGI(TAG, "节点配网成功");
            break;
        case ESP_BLE_MESH_NODE_EVT:
            ESP_LOGI(TAG, "节点收到消息");
            break;
        default:
            break;
    }
}

void app_main(void) {
    esp_err_t ret;

    // 初始化 NVS
    ret = nvs_flash_init();
    if (ret == ESP_ERR_NVS_NO_FREE_PAGES) {
        ESP_ERROR_CHECK(nvs_flash_erase());
        ret = nvs_flash_init();
    }
    ESP_ERROR_CHECK(ret);

    // 初始化蓝牙
    ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT));

    esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
    ESP_ERROR_CHECK(esp_bt_controller_init(&bt_cfg));
    ESP_ERROR_CHECK(esp_bt_controller_enable(ESP_BT_MODE_BLE));

    // 初始化 Bluedroid
    ESP_ERROR_CHECK(esp_bluedroid_init());
    ESP_ERROR_CHECK(esp_bluedroid_enable());

    // 初始化 Mesh
    esp_ble_mesh_init(mesh_callback);

    // 开始配网
    ESP_ERROR_CHECK(esp_ble_mesh_provisioner_add_unprov_dev(
        dev_uuid, sizeof(dev_uuid),
        0, 0, NULL, 0
    ));

    ESP_LOGI(TAG, "蓝牙 Mesh 节点启动完成");
}

编译烧录

# 编译项目
idf.py build

# 烧录到 ESP32(替换 /dev/ttyUSB0 为你的设备端口)
idf.py -p /dev/ttyUSB0 flash monitor

组网配置

1. 配网(Provisioning)

使用手机 App(如 nRF Mesh)进行配网:

  1. 打开 nRF Mesh App,扫描未配网设备:App 会自动发现附近处于未配网状态的蓝牙 Mesh 设备(Unprovisioned Device)。

  2. 点击目标设备,选择 “Identify”:App 会发送 Identify 动作,设备上的 LED 会闪烁确认身份。

  3. 输入配网密钥(Network Key):如果是首次配网,App 会自动生成网络密钥;如果需要加入已有网络,输入预先配置的密钥。

  4. 分配单播地址:App 自动为设备分配唯一的单播地址(如 0x0001、0x0002),配网完成,设备正式上线。

2. 配置发布/订阅

在 App 中配置:

设备:Living Room Light
├─ 订阅地址:0xC001(群组地址)
└─ 发布地址:0x0001(自身单播地址)

设备:Bedroom Sensor
├─ 订阅地址:0x0002(自身单播地址)
└─ 发布地址:0xC001(群组地址)

这样,传感器数据发布到 0xC001,所有订阅该地址的设备都能收到。

3. 创建场景

场景是一组预定义的操作:

场景名称:离家模式
触发条件:按下场景开关
执行动作:
  - 关闭客厅灯(地址 0x0001)
  - 关闭卧室灯(地址 0x0002)
  - 开启安防模式(地址 0x0003)

常见问题排查

问题 1:设备无法配网

症状:手机 App 扫描不到设备

排查步骤

  1. 检查设备是否已上电并处于未配网状态:新设备或已擦除 NVS 的设备才能被扫描到。如果设备已配网,需要先执行 Reset Node 清除配网信息。

  2. 确认蓝牙已开启且手机靠近设备:配网时手机需要在设备蓝牙范围内(通常 10 米内)。远离干扰源(如 WiFi 路由器、微波炉)。

  3. 检查设备固件是否正确烧录:确认 ESP32 上运行的是正确的 Mesh 节点固件,串口日志中应有 “未配网设备广播” 的输出。

  4. 尝试重启设备和手机 App:有时蓝牙协议栈会卡住,重启双方后再试。如果还不行,用 idf.py erase-flash 擦除 ESP32 后重新烧录。

问题 2:消息无法传递

症状:设备已配网,但控制指令无响应

排查步骤

  1. 检查发布/订阅地址是否匹配:发送方发布地址和接收方订阅地址必须一致。用 nRF Mesh App 查看设备的 Model 配置,确认地址设置正确。

  2. 确认 TTL 值足够大:TTL(Time To Live)决定消息能经过多少次中继。如果设备距离远或中间有障碍物,TTL 需要设为 3-5 才能到达。

  3. 检查网络密钥(NetKey)和应用密钥(AppKey):所有参与通信的设备必须使用相同的密钥。密钥不匹配会导致消息被丢弃。

  4. 查看串口日志确认消息是否到达:在接收端 ESP32 的串口输出中,查看是否有收到消息的日志。如果没收到,说明消息根本没到达该节点。

问题 3:功耗过高

症状:电池供电设备续航短

优化方案

  1. 启用 LPN(Low Power Node)模式:电池供电设备配置为 LPN,大部分时间处于深度睡眠,只在固定间隔唤醒接收消息。功耗可从 mA 级降到 μA 级。

  2. 降低消息发送频率:传感器数据不需要实时上报,每 10 秒或 30 秒发送一次即可。用 esp_deep_sleep 在发送间隔期间休眠。

  3. 关闭不必要的功能:关闭日志输出(ESP_LOGI 改为 ESP_LOG_NONE)、禁用经典蓝牙、降低 CPU 频率到 80MHz,都能显著降低功耗。

  4. 使用友元密钥(Friend Key):LPN 节点通过友元节点缓存消息,自己只在固定时间窗口唤醒取消息,大幅减少射频开启时间。

// 进入深度睡眠(10 秒后唤醒)
esp_deep_sleep(10 * 1000000ULL);

问题 4:覆盖范围不足

症状:远距离设备通信不稳定

解决方案

  1. 增加中继节点:在信号弱的区域部署中继节点(Relay Node),帮助转发消息。ESP32 开发板便宜,多加几个节点成本很低。

  2. 提高发射功率:在 sdkconfig 中将蓝牙发射功率从 0dBm 提高到 9dBm(ESP32 最大值),覆盖范围可提升 2-3 倍。

  3. 优化天线位置和方向:确保 ESP32 板载天线没有被金属遮挡,天线方向垂直于地面。必要时外接 SMA 天线增强信号。

  4. 调整网络拓扑:将关键设备(如网关、场景开关)放在中心位置,其他设备围绕部署,减少消息跳数。避免设备排列成直线导致末端信号弱。

让我们搭建一个完整的智能灯光系统:

系统架构

graph TD
    Switch["场景开关<br/>(0x0001)"]
    Gateway["网关节点<br/>(0x0002)"]
    Phone["手机 App"]
    Light1["客厅灯<br/>(0x0003)"]
    Light2["卧室灯<br/>(0x0004)"]
    Light3["厨房灯<br/>(0x0005)"]

    Switch -->|发布指令| Gateway
    Phone -->|发布指令| Gateway
    Gateway -->|群组 0xC001| Light1
    Gateway -->|群组 0xC001| Light2
    Gateway -->|群组 0xC001| Light3

配置步骤

  1. 为每个灯具节点配网并分配地址:用 nRF Mesh App 依次配网客厅灯(0x0003)、卧室灯(0x0004)、厨房灯(0x0005),为每个节点配置 Generic OnOff Server Model。

  2. 创建群组地址并设置订阅:创建群组地址 0xC001(全屋灯光),让所有灯具节点订阅该地址。再创建 0xC002(客厅)、0xC003(卧室)等区域群组,实现分区控制。

  3. 配置场景开关的发布地址:场景开关订阅场景触发地址,发布到对应群组地址。按下”离家模式”按钮时,开关发布到 0xC001,所有灯具收到关闭指令。

添加传感器实现自动化:

如果 光照传感器 < 100lux 且 人体传感器 = 检测到
则 开启对应区域灯光(延迟 30 秒自动关闭)

性能优化建议

1. 网络拓扑优化

  • 星型拓扑:适合小范围(<50 平米)

  • 网状拓扑:适合大范围,启用中继

  • 混合拓扑:关键路径用网状,边缘用星型

2. 消息优先级

// 高优先级消息(立即发送)
esp_ble_mesh_generic_server_publish(..., TTL_HIGH);

// 低优先级消息(可延迟)
esp_ble_mesh_generic_server_publish(..., TTL_LOW);

3. 安全加固

  • 启用应用密钥加密

  • 定期更新网络密钥

  • 限制配网时间窗口

  • 使用 OOB 认证(如二维码)

总结

蓝牙 Mesh 是搭建智能家居的务实选择。它成本低、覆盖广、功耗低,而且不需要额外网关。

关键要点:

  • 理解发布/订阅机制是组网的核心

  • 合理规划网络拓扑和地址分配

  • 低功耗场景启用 LPN 模式

  • 信号弱的区域增加中继节点

200 块的硬件投入,就能搭建一套覆盖全屋的智能控制系统。剩下的就是发挥创意,把你的想法变成现实。

希望这篇博客文章对您有所帮助!