基于esp32h2的matter开发记录

1.创建一个matter工程

在 ESP-IDF 环境下从零创建一个 Matter 工程,最稳妥、最快的方法是基于官方示例(Example)进行复制和魔改。因为 Matter 工程涉及大量的底层配置、蓝牙/Thread 协议栈初始化以及数据模型(Data Model)定义,从头手写 CMakeLists.txt 极其容易出错。
(1) 环境准备
在开始前,请确保你的终端已经正确导出了 ESP-IDF 和 ESP-Matter 的环境变量。

#1. 导出 ESP-IDF 环境(以 v5.2 或 v5.3 为例)
. $HOME/esp/esp-idf/export.sh
#2. 导出 ESP-Matter 环境
. $HOME/esp/esp-matter/export.sh

(2) 创建工程目录
官方提供了一个高度模版化的工具工程。我们可以直接从 esp-matter 的克隆目录中,将基础示例复制到你的工作区。

#复制官方的 light 示例作为你的新工程模板 
cp -r $ESP_MATTER_PATH/examples/light ./esp32h2_matter_light

(3) 项目全局重命名(可选)
默认复制过来的项目名称可能叫 light。如果你想改成自己的项目名(例如 my_smart_light):

  • 打开项目根目录下的 CMakeLists.txt(注意:不是 main 目录下的);
  • 找到 project(light) 这一行;
  • 修改为你新项目的名字:project(my_smart_light) # 修改这里。

(4) 设置目标芯片
针对你的芯片(比如 ESP32-H2),需要切换编译目标并进行基础的菜单微调(如关闭引发链接错误的 Delta OTA 功能)。

#1. 清除可能残留的旧缓存
idf.py fullclean
#2. 设置目标芯片为 esp32h2
idf.py set-target esp32h2

(5) 配置工程
修改FreeRTOS节拍配置(vTaskDelay延时10ms改为1ms)。在FreeRTOS的默认配置中,任务调度的频率默认是100HZ,因此默认vTaskDelay默认延时是10ms。 FreeRTOS 的系统时钟节拍可以在配置文件 FreeRTOSConfig.h 里面设置:

  • 通过cd命令,进入到我们工程目录文件夹,即sdkconfig文件所在路径。然后输入idf.py menuconfig命令;
  • 我们在这里可以可视化地对sdkconfig中的参数进行配置;
  • 按下“/”进行搜索;
  • 找到FREERTOS_HZ修改为1000即可。

(6) 编译与烧录
一切就绪后,执行构建命令。由于 Matter 包含完整的开源 Connectedhomeip 仓库,首次编译会非常慢(可能需要 5-15 分钟,取决于电脑性能),后续增量编译就会非常快。

idf.py build

编译成功后,通过idf.py flash monitor命令实现程序的下载和串口监控。

2.自定义组件

(1) 创建components
在esp32h2_matter_light目录下创建components文件夹,在components文件夹下创建app和hal两个文件夹,再在app和hal文件夹下创建inc和src文件夹放置头文件和源文件。
(2) 编辑CMakeLists.txt
在components文件夹下创建CMakeLists.txt,编写内容:

include($ENV{ESP_MATTER_DEVICE_PATH}/esp_matter_device.cmake)

set(src_dirs
            app/src
            hal/src)

set(include_dirs
            app/inc
            hal/inc)

set(requires
            driver
            esp_timer
            esp_adc
            nvs_flash
            console
            esp_matter)

idf_component_register(SRC_DIRS ${src_dirs} INCLUDE_DIRS ${include_dirs} REQUIRES ${requires})

(3) 注意点:
如果源文件是.c文件,需要在.h文件前面位置加上#ifdef __cplusplus extern "C" { #endif,还有在最后面加上#ifdef __cplusplus } #endif,否则会报找不到调用函数。

3.uart功能

esp32h2的串口有uart0和uart1,uart0默认用来下载调试,默认引脚是GPIO23和GPIO24,uart1引脚可以自定义映射。
(1) usart1.h

#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include "driver/gpio.h"
#include "driver/uart.h"
#include "driver/uart_select.h"
/* 引脚和串口定义 */
#define USART_UX UART_NUM_1
#define USART_TX_GPIO_PIN GPIO_NUM_4
#define USART_RX_GPIO_PIN GPIO_NUM_5
/* 串口接收相关定义 */
#define RX_BUF_SIZE 1024 /* 环形缓冲区大小 */
/* 函数声明*/
void usart_init(uint32_t baudrate); /* 初始化串口 */
#ifdef __cplusplus
}
#endif

(2) usart.c

#include <stdio.h>
#include "usart.h"
/**
* @brief 初始化串口
* @param baudrate: 波特率,根据自己需要设置波特率值
* @note 注意:必须设置正确的时钟源,否则串口波特率就会设置异常
* @retval 无
*/
void usart_init(uint32_t baudrate)
{
	uart_config_t uart_config; /* 串口配置句柄 */
	uart_config.baud_rate = baudrate; /* 波特率 */
	uart_config.data_bits = UART_DATA_8_BITS; /* 字长为8位数据格式 */
	uart_config.parity = UART_PARITY_DISABLE; /* 无奇偶校验位 */
	uart_config.stop_bits = UART_STOP_BITS_1; /* 一个停止位 */
	uart_config.flow_ctrl = UART_HW_FLOWCTRL_DISABLE; /* 无硬件控制流 */
	uart_config.source_clk = UART_SCLK_DEFAULT; /* 配置时钟源 */
	uart_config.rx_flow_ctrl_thresh = 122; /* 硬件控制流阈值 */
	uart_param_config(USART_UX, &uart_config); /* 配置uart端口 */
	/* 配置uart引脚 */
	uart_set_pin(USART_UX, USART_TX_GPIO_PIN, USART_RX_GPIO_PIN, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
	// 如果外部设备(如 XP2116)在空闲时没有稳定的高电平,建议在 uart_set_pin 后手	动开启 RX 引脚的上拉,防止产生噪声触发 UART_BREAK
	gpio_pullup_en(USART_RX_GPIO_PIN); 
	/* 安装串口驱动 */
	uart_driver_install(USART_UX,
	RX_BUF_SIZE * 2,
	RX_BUF_SIZE * 2,
	20,
	NULL,
	0);
}

4.adc功能

ESP32-H2 拥有一个内置的 SAR ADC(逐次逼近寄存器模数转换器)。虽然它是一款主打 Thread/Zigbee/BLE 5.4 的无线 SoC,但其低功耗的外设 ADC 性能足够满足绝大多数传感器数据采集、电池电压监测等日常开发需求。
(1) ADC引脚对应关系
ESP32-H2 的 ADC1 共有 5 个通道,分布在以下 GPIO 上。在规划硬件电路时,请务必使用这几个特定引脚:

ADC1_CH0 -> GPIO0
ADC1_CH1 -> GPIO1
ADC1_CH2 -> GPIO2
ADC1_CH3 -> GPIO3
ADC1_CH4 -> GPIO4

(2) 衰减器(Attenuation)与测量电压范围
ADC 内部的量程是有限的。为了测量更高的电压,需要配置衰减器。
根据乐鑫官方设计规范,ESP32-H2 在不同衰减下的建议测量范围如下:
衰减配置 (Attenuation) 建议输入电压范围 实际满量程电压(参考)

ADC_ATTEN_DB_0	 0 mV ~ 950 mV	约 950 mV
ADC_ATTEN_DB_2_5 0 mV ~ 1250 mV	约 1250 mV
ADC_ATTEN_DB_6	 0 mV ~ 1750 mV	约 1750 mV
ADC_ATTEN_DB_11	 0 mV ~ 2800 mV	约 2800 mV

(3) 重要安全提醒:
无论选择何种衰减,GPIO 上的绝对输入电压都不能超过 VDD_A(通常为 3.3V),否则会永久烧毁芯片引脚。如果需要测量 3.3V 以上的电压(如 3.7V 锂电池),必须搭建外部分压电阻电路。
(4) adc.h

#ifndef _HAL_ADC_H_
#define _HAL_ADC_H_
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
/* Exported macro ------------------------------------------------------------*/
/* ADCs */
typedef enum
{
	HAL_AIN_V_DIP = 0,
	HAL_AIN_NUM,
	HAL_AIN_NULL
} hal_ain_t;
// 定义使用的 ADC 通道
#if 1
#define V_DIP_ADC_CH ADC_CHANNEL_1 // 对于 ESP32-H2, ADC1_CH1 是 GPIO2
#else
#define V_DIP_ADC_CH ADC_CHANNEL_2 // 对于 ESP32-H2, ADC1_CH2 是 GPIO3
#endif
/* Exported functions --------------------------------------------------------*/
void hal_adc_init(void);
u16 hal_adc_read(hal_ain_t ain);
u16 hal_adc_get_mv(u16 adc_value);
u16 hal_adc_data_filter(u16 *adc_data, u8 size);
#ifdef __cplusplus
}
#endif
#endif/*_HAL_ADC_H_*/

(5) adc.c

/* Includes ------------------------------------------------------------------*/
#include "hal_drv.h"

/* Private variables ---------------------------------------------------------*/
adc_oneshot_unit_handle_t adc1_handle;
adc_cali_handle_t cali_handle = NULL;

const adc_channel_t adc_channel[HAL_AIN_NUM] = {
    V_DIP_ADC_CH
};

/* Private macro -------------------------------------------------------------*/

void hal_adc_init(void)
{
    // --- 1. ADC 单元初始化 ---
    adc_oneshot_unit_init_cfg_t init_config1 = {
        .unit_id = ADC_UNIT_1,
    };
    ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config1, &adc1_handle));

    // --- 2. ADC 通道配置 ---
    adc_oneshot_chan_cfg_t config = {
        .atten = ADC_ATTEN_DB_12,  // 12dB 衰减,支持 0 ~ 2.8V 左右的范围
        .bitwidth = ADC_BITWIDTH_DEFAULT, // ESP32-H2 默认为 12位
    };

    for (u8 i = 0; i < HAL_AIN_NUM; i++) {
        ESP_ERROR_CHECK(adc_oneshot_config_channel(adc1_handle, adc_channel[i], &config));
    }

    // --- 3. ADC 校准初始化 (获取准确的 mV) ---
    adc_cali_curve_fitting_config_t cali_config = {
        .unit_id = ADC_UNIT_1,
        .atten = ADC_ATTEN_DB_12,  // 12dB 衰减,支持 0 ~ 2.8V 左右的范围
        .bitwidth = ADC_BITWIDTH_DEFAULT,
    };
    // ESP32-H2 支持 Line Fitting 校准方案
    ESP_ERROR_CHECK(adc_cali_create_scheme_curve_fitting(&cali_config, &cali_handle)); 
}

u16 hal_adc_read(hal_ain_t ain)
{
    int adc_value = 0;

    ESP_ERROR_CHECK(adc_oneshot_read(adc1_handle, adc_channel[ain], &adc_value));

    return (u16)adc_value;
}

u16 hal_adc_get_mv(u16 adc_value)
{
    int voltage = 0;

    ESP_ERROR_CHECK(adc_cali_raw_to_voltage(cali_handle, adc_value, &voltage));  // 相对于adc, voltage有校准,值更准

    return (u16)voltage;
}

u16 hal_adc_data_filter(u16 *adc_data, u8 size)
{
    u16 adc_min = 0, adc_values = 0, adc_value = 0;
    u8 i = 0, j = 0;
    u8 adc_min_idx = 0;
    u8 is_adc_min_found = 0;
    u8 skip_num = 0;

	// sort adc rawdata
    for (i = 0; i < size; i++) {
        adc_min = adc_data[i];

        // find the min rawdata
        for (j = i + 1; j < size; j++) {
            if (adc_data[j] < adc_min) {
                adc_min = adc_data[j];
                adc_min_idx = j;
                is_adc_min_found = 1;
            }
        }

        // exchange the min rawdata
        if (is_adc_min_found) {
            u16 tmp = adc_data[i];
            adc_data[i] = adc_data[adc_min_idx];
            adc_data[adc_min_idx] = tmp;
            is_adc_min_found = 0;
        }
    }

    if (size > 12) {
        skip_num = size - 8;
    } else if (size > 4) {
        skip_num = size - 4;
    } else {
        skip_num = 0;
    }

    // filter the front 4 rawdatas and the tail 4 rawdatas 
    for (i = (skip_num >> 1); i < (size - (skip_num >> 1)); i++) {
        // filter the front 4 rawdatas and the tail 4 rawdatas 
        adc_values += adc_data[i];
    }
    adc_value = adc_values / (size - ((skip_num >> 1) << 1));

    return adc_value;
}

5.flash功能

NVS (Non-volatile Storage) 是乐鑫官方最推荐的数据存储方式。它类似于一个微型的数据库,以键值对(Key-Value)的形式存储数据(例如:键 “WiFi_Pass” -> 值 “12345678”)。
(1) 自带擦写均衡(Wear Leveling)
Flash 单个扇区只有约 10 万次寿命。NVS 在底层会自动把数据分散写入不同的物理扇区,防止死抓着一个地方擦写导致 Flash 局部报废。
(2) 断电保护
在写入新值时,NVS 会先写新数据,确认成功后再标记旧数据失效。即使在写入瞬间设备突然断电,数据也不会损坏或丢失。
(3) 格式丰富
支持 u8、i32、string(字符串)以及 blob(任意格式的二进制大对象,如结构体)。
(4) flash.h

#ifndef _HAL_FLASH_H_
#define _HAL_FLASH_H_
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
/* Exported functions --------------------------------------------------------*/
void hal_flash_read(const char *name, const char* key, u8 *buff, u16 len);
void hal_flash_write(const char *name, const char* key, u8 *buff, u16 len);
#ifdef __cplusplus
}
#endif
#endif/*_HAL_FLASH_H_*/

(5) flash.c

/* Includes ------------------------------------------------------------------*/
#include "hal_drv.h"
#include "nvs_flash.h"
#include "nvs.h"

/* Private macro -------------------------------------------------------------*/
static nvs_handle_t nvm_handle = 0;  // nvs句柄
static const char *TAG = "[FLASH]";

void hal_flash_read(const char *name, const char* key, u8 *buff, u16 len)
{
    bool is_nvs_valid = false;

    if (nvs_open(name, NVS_READONLY, &nvm_handle) == ESP_OK) {
        is_nvs_valid = true;
    } else {
        // 如果是第一次写入,必须使用读写模式,否则无法创建命名空间
        ESP_LOGE(TAG, "使用读写模式打开 NVS 命名空间");
        if (nvs_open(name, NVS_READWRITE, &nvm_handle) == ESP_OK) {
            is_nvs_valid = true;
        }
    }

    if (is_nvs_valid) {
        // 读取到 NVM_BUFF 数组中
        size_t nvm_size = len; // 必须指定缓冲区大小
        esp_err_t err = nvs_get_blob(nvm_handle, key, buff, &nvm_size);
        // if (err == ESP_OK) {
        //     ESP_LOGI(TAG, "读取成功!");
        // } else if (err == ESP_ERR_NVS_NOT_FOUND) {
        //     ESP_LOGI(TAG, "数据尚未保存过!");
        // } else {
        //     ESP_LOGI("TAG", "读取失败,错误码: %s (0x%x)", esp_err_to_name(err), err);
        // }
    }

    nvs_close(nvm_handle);  // 关闭存储句柄并释放所有已分配的资源
}

void hal_flash_write(const char *name, const char* key, u8 *buff, u16 len)
{
    if (nvs_open(name, NVS_READWRITE, &nvm_handle) == ESP_OK) {
        // 直接将数组名作为指针传入
        // 长度为:数组元素个数 * 每个元素的大小
        nvs_set_blob(nvm_handle, key, buff, len);
        nvs_commit(nvm_handle);  // 写入flash
        nvs_close(nvm_handle);
    }
}

6.matter量产分区生成工具

esp-matter-mfg-tool帮助生成与Matter兼容的制造和安全证书分区。制造分区可以包含特定于Matter的数据,也支持使用csv文件添加特定于制造商的定制数据。
(1) 什么是Matter量产分区
在 Matter 协议中,每台设备都必须有唯一的“身份证”。这个工具的作用,就是为你批量生成包含这些身份证信息的 NVS 固件文件(.bin),然后通过烧录器分别烧进每台 ESP32-H2 的 Flash 中。
(2) 使用pip安装
esp-matter-mfg-tool可以使用Python的package installer安装

python3 -m pip install esp-matter-mfg-tool

(3) 配置应用

cd <your_app>
idf.py menuconfig

在配置菜单中,设置以下附加配置以使用自定义工厂分区,并为数据和设备信息提供程序设置不同的值。

  • Component config → CHIP Device Layer → Commissioning options → Use ESP32 Factory Data Provider
  • Component config → CHIP Device Layer → Commissioning options → Use ESP32 Device Instance Info Provider
  • Component config → ESP Matter → Device Instance Info Provider options → Device Instance Info - Factory 或Component config → ESP Matter → DAC Provider options → Attestation - Secure Cert
  • Component config → CHIP Device Layer → Matter Manufacturing Options → chip-factory namespace partition label,选择要在“chip-factory”命名空间中存储键值的分区的标签。默认分区标号为nvs。

(4) 用例
下面的命令使用测试PAI签名证书和密钥、Matter SDK中存在的测试证书声明、供应商ID: 0xFFF2和产品ID: 0x8001。

  • 导出Matter SDK路径,简化证书和密钥路径。
export MATTER_SDK_PATH=$ESP_MATTER_PATH/connectedhomeip/connectedhomeip
  • 生成工厂分区
esp-matter-mfg-tool -v 0xFFF2 -p 0x8001 --vendor-name "test vendor" \
    --product-name "test product" --hw-ver 1 --hw-ver-str "harware version" --pai \
    -k $MATTER_SDK_PATH/credentials/test/attestation/Chip-Test-PAI-FFF2-8001-Key.pem \
    -c $MATTER_SDK_PATH/credentials/test/attestation/Chip-Test-PAI-FFF2-8001-Cert.pem \
    -cd $MATTER_SDK_PATH/credentials/test/certification-declaration/Chip-Test-CD-FFF2-8001.der
  • 生成5个工厂分区[可选参数:-n]
esp-matter-mfg-tool -n 5 -v 0xFFF2 -p 0x8001 --vendor-name "test vendor" \
    --product-name "test product" --hw-ver 1 --hw-ver-str "harware version" --pai \
    -k $MATTER_SDK_PATH/credentials/test/attestation/Chip-Test-PAI-FFF2-8001-Key.pem \
    -c $MATTER_SDK_PATH/credentials/test/attestation/Chip-Test-PAI-FFF2-8001-Cert.pem \
    -cd $MATTER_SDK_PATH/credentials/test/certification-declaration/Chip-Test-CD-FFF2-8001.der
  • 烧录生成的制造二进制文件
    请注意,esp-matter-mfg-tool只生成,需要使用esptool.py将其烧录到设备上的制造二进制映像
  • 向设备烧录二进制图像
esptool.py -p <serial_port> write_flash <address> path/to/<uuid>-partition.bin

例如,

esptool.py write_flash 0x3e0000 out/xxx-partition.bin
  • 注意:首先烧录你的应用程序固件,然后在设备上自定义分区二进制。请在CHIP_FACTORY_NAMESPACE_PARTITION_LABEL(默认为nvs)设置的已配置工厂分区的相应地址上刷新制造二进制文件。
  • 调测设备
    物质专员的QR码生成于out/<vid_pid>//-qrcode.png。如果QR码不可见,将下面的链接粘贴到浏览器中,将<qr_code>替换为QR码字符串(例如:MT: Y。K9042C00KA0648G00-这也是默认测试二维码)和扫描二维码。

7.chip-tool调试

(1) 在交互模式下使用chip-tool进行设备配网:

chip-tool interactive start
pairing ble-thread 0x7283 0x31 20202021 3840

执行此指令后会进入配网状态,最后一句提示Run command failure,但不影响chip-tool 指令调试,不过运行一段时间后会自动断连,需再次执行配网指令。
(2) 自动断连分析
在 Matter 的规范里,新设备(你的 ESP32-H2)在刚上电时,是通过 蓝牙(BLE) 广播自己的。当你运行 chip-tool pairing ble-thread … 命令时,你的电脑(通过蓝牙)连接上了 ESP32-H2。chip-tool 通过蓝牙把操作凭证、网络密钥发给H2,这个过程叫配网(Commissioning)。
关键点来了:在配网刚刚完成的几十秒内,chip-tool 与 ESP32-H2 之间的蓝牙连接通道并没有立刻切断。此时你发送控制命令(比如开关灯),chip-tool 会非常聪明地通过仍然存活的蓝牙通道直接把命令送达设备。所以你觉得“能控制”。
蓝牙超时关闭:配网成功大约 30 秒到 1 分钟后,ESP32-H2 的 Matter 协议栈为了省电和释放内存,会主动断开并关闭蓝牙配网广播。
切换到 Thread 运营网络:此时,chip-tool 会试图通过主流的 IPv6 运营网络(即 Thread 协议栈) 去和 ESP32-H2 通信。
致命死锁:你的电脑(运行 chip-tool)目前处在普通的 Wi-Fi 或以太网(IPv4/IPv6)中,而 ESP32-H2 此时切换到了 Thread 15.4 无线网络中。由于没有边界路由器在中间做 Wi-Fi 到 Thread 的数据包翻译和路由转发,你的电脑发出的 Thread 数据包根本出不去,H2 的回包电脑也收不到。
蓝牙一关,Thread 又不通,chip-tool 就会瞬间报错:PANIC: Device not found 或 TIMEOUT。
(3) 解决方案

  • 自建一个 OpenThread 边界路由器 (OTBR)
    这是最根本、最稳定的解决方案,尤其适合开发者或需要长期稳定调试的场景。其核心原理是搭建一个“翻译官”设备,它同时连接你的家庭 Wi-Fi 网络和 ESP32-H2 所在的 Thread 低功耗无线网络,实现两种不同网络协议之间的数据包转换和路由。

    1. 硬件准备:

    • 主控芯片 (Wi-Fi + 应用处理器):推荐使用 ESP32-S3。它具备强大的 Wi-Fi 和蓝牙功能,以及足够的计算能力和外设接口(如 SPI),非常适合作为边界路由器的主控。
    • 协处理器 (Thread 无线电):使用 ESP32-H2。它的核心价值在于内置了符合 Thread 1.3 标准的 802.15.4 射频模块,专门负责处理 Thread 网络协议栈。
    • 连接方式:两块开发板之间通过 SPI (Serial Peripheral Interface) 进行高速通信。你需要将 ESP32-S3 的 SPI 主机引脚(MOSI, MISO, SCLK, CS)与 ESP32-H2 的 SPI 从机引脚对应连接。此外,还需要连接一根 GPIO 作为中断线,用于 H2 向 S3 通知数据到达。

    2. 软件准备:

    • 获取官方示例工程:乐鑫在 esp-thread-br 仓库中提供了完整的边界路由器参考实现。你可以通过以下命令克隆:
      git clone --recursive https://github.com/espressif/esp-thread-br.git
      
    • 环境配置:确保你的开发环境已设置好 ESP-IDF(针对 ESP32-S3)和 ESP-Matter(包含 Thread 协议栈)。进入 esp-thread-br 目录,分别对 host (S3) 和 rcp (H2) 两个子工程进行配置和编译。
    • 关键配置项:在 idf.py menuconfig 中,需要正确配置:
      • Wi-Fi 连接信息:设置边界路由器要连接的 Wi-Fi SSID 和密码。
      • Thread 网络参数:如网络名称(Network Name)、PAN ID、通道等。通常可以使用默认值或自动生成。
      • SPI 引脚映射:确保与你的硬件连接一致。

    3. 烧录与运行:

    • 分别将编译好的固件烧录到 ESP32-S3 和 ESP32-H2。
    • 上电后,ESP32-S3 会启动 Wi-Fi Station 模式连接到你的路由器,同时通过 SPI 初始化 ESP32-H2 作为 Radio Co-Processor (RCP)。
    • ESP32-H2 会形成一个 Thread 网络,并作为该网络的 Leader 或 Router。
    • 此时,你的 ESP32-H2 Matter 设备(即之前调试的灯)可以加入到这个 Thread 网络中。
    • 最关键的一步:边界路由器(OTBR)会运行一个 “边界路由器代理” 服务。这个服务会在 Wi-Fi 侧(你的局域网)发布一个 mDNS 服务(_meshcop._udp),宣告自己是一个 Thread 网络的入口。

    4. 对 chip-tool 的影响:
    当你的电脑(运行 chip-tool)和 OTBR 在同一个 Wi-Fi 局域网时,chip-tool 通过 mDNS 就能自动发现这个 Thread 边界路由器。此后,chip-tool 发送给 ESP32-H2 Matter 设备的所有命令,都会先通过 Wi-Fi 发送到 OTBR,再由 OTBR 通过 SPI 转发给 ESP32-H2 的 Thread 网络。同样,设备的响应也会沿原路返回。
    这样一来,无论蓝牙是否超时关闭,chip-tool 和设备之间都有一条永久、稳定的 IP 路由通道,彻底解决了“断连”问题。

    优点:

    • 完全本地化:不依赖任何第三方网关(如苹果 HomePod)。
    • 控制权完全在手:网络参数、安全性均可自定义。
    • 调试体验最佳:命令响应稳定,适合长期开发和测试。

    缺点:

    • 硬件成本增加:需要额外准备 ESP32-S3 和 H2 开发板,并进行硬件连接。
    • 配置稍复杂:需要理解并配置双芯片工程。

    总结:自建 OTBR 是解决 Matter over Thread 设备调试“最后一公里”问题的终极方案。它虽然需要一些额外的硬件和配置工作,但能为后续的产品开发、功能测试和稳定性验证提供最可靠的基础设施。
    硬件准备:准备一块 ESP32-S3(带 Wi-Fi)和一块 ESP32-H2(带 Thread),把它们俩的 SPI 引脚互相连起来。
    烧录官方 Demo:乐鑫官方提供了一个叫 esp-thread-br(边界路由器)的工程。烧录进去后,这块双芯片板就会变成一个标准的边界路由器。它会将你家里的 Wi-Fi 网络和 H2 的 Thread 网络无缝打通。此时 chip-tool 就能永久控制了。

  • 利用现有的商业生态网关
    由于我们没有自建的边界路由器,必须让自带边界路由器的 HomePod Mini 来做第一个领路人。首先将HomePod Mini添加至iPhone的“家庭”(Home)APP,接着用iPhone扫描ESP32H2设备的QR Code连接设备。
    现在我们要利用 Matter 的 Multi-Admin 特性,让苹果网关命令 ESP32-H2 生成一个“临时通行证”,以便 chip-tool 接入。在 iPhone “家庭” App 中,长按或点击进入刚刚添加的智能灯设备设置页面。滑到最底部,点击 “开启配对模式” (Turn On Pairing Mode)。此时,苹果系统会弹出一个新的 11 位手动配对码(Manual Pairing Code)(注意:这个码是动态生成的,不同于出厂自带的那个)。回到你的电脑终端,使用 chip-tool 强行并入这个设备。
    运行以下命令(注意替换参数):

#命令格式:chip-tool pairing code<你指定的Node_ID> <苹果给你的11位临时配对码>
pairing code 0x7283 33709254812

此时,chip-tool 会在局域网内通过 IPv6 多播(mDNS)广播寻找这个处于配对状态的设备。HomePod Mini 收到请求后,会把数据无缝路由给 Thread 网络上的 ESP32-H2。H2 校验临时配对码成功后,就会与 chip-tool 建立安全的 CASE 运营连接。
当 chip-tool 终端提示 Device commissioning completed successfully 后,双入网正式大功告成!
这就是标准 Matter 协议带来的 Multi-Admin 魅力,它们在没有外部云端参与的情况下,在本地局域网内完美实现了多生态并存控制。

8.Matter OTA

(1) 生成CHIP OTA图像
所有Matter软件图像必须包含规范第11.21.1节中定义的标头。ota_image_tool可用于在软件映像上生成所需的标头。
提供给OTA提供商应用程序的所有图像(通过——filepath或——otaImageList)必须包含软件图像标头。OTA Provider应用程序将使用报头中指定的软件版本来设置queryimagerresponse的SoftwareVersion字段。
用户可以通过简单地启用CONFIG_CHIP_OTA_IMAGE_BUILD配置选项来生成Matter OTA图像。在名为<项目名称>-ota.bin的构建目录中生成OTA映像。然后该映像可以与OTA提供程序应用程序一起使用。请确保版本号设置为正确的值。使用CONFIG_DEVICE_SOFTWARE_VERSION和CONFIG_DEVICE_SOFTWARE_VERSION_NUMBER配置选项设置软件版本。
也可以通过ota_image_tool.py生成OTA映像。例如提供代表软件版本2的映像,使用 ota_image_tool.py 脚本生成 Matter OTA 镜像。ota_image_tool.py在路径esp-matter/connectedhomeip/connectedhomeip/src/app中。
则该工具可以如下使用:

src/app/ota_image_tool.py create -v 0xDEAD -p 0xBEEF -vn 2 -vs "2.0" -da sha256 firmware.bin firmware.ota

这里需要注意firmware.bin文件开头的第一个字节到底是不是0xE9。ota_image_tool会在软件映像firmware.bin包裹所需的标头生成firmware.ota。如果firmware.bin已经包含标头会导致解析第一个字节(0xE9)出错。
在Linux环境下,直接用hexdump打印使用的firmvare.ota的前几百个字节进行确认。

hexdump -C firmware.ota | head -n 30

(2) 编译生成chip-ota-provider-app
进入路径:cd esp-matter/connectedhomeip/connectedhomeip/,如果examples文件夹下没有ota-provider-app文件夹,可以从github上下载完整的connectedhomeip,点击Code,选择Download ZIP即可。将connectedhomeip-master\examples文件夹下的ota-provider-app复制到esp-matter/connectedhomeip/connectedhomeip/examples路径下。
执行编译指令

scripts/examples/gn_build_example.sh examples/ota-provider-app/linux out/debug chip_config_network_layer_ble=false

编译成功会在out/debug路径下生成chip-ota-provider-app。
(3) 启动provider
使用chip-ota-provider-app启动provider

out/debug/chip-ota-provider-app --discriminator 3841 --secured-device-port 5560 --KVS /tmp/chip_kvs_provider_clean --filepath firmware.ota

在chip-tool中加入provider

pairing onnetwork 0xDEADBEEF 20202021

(4) 配置权限(ACL)
专员或管理员应在调试时或之后安装必要的ACL条目,以便在其结构上处理来自OTA请求者的QueryImage命令,否则OTA请求者将无法使用OTA提供程序。
因为ACL属性包含一个ACL条目列表,所以属性的写入不应该只包含新条目的值。任何现有的条目都应该被读取,并作为写入的一部分。下面是一个如何编写ACL属性的示例,包含两个条目:

out/chip-tool accesscontrol write acl '[{"fabricIndex": 1, "privilege": 5, "authMode": 2, "subjects": [112233], "targets": null}, {"fabricIndex": 1, "privilege": 3, "authMode": 2, "subjects": null, "targets": [{"cluster": 41, "endpoint": null, "deviceType": null}]}]' 0xDEADBEEF 0

条目1:这是作为调试的一部分创建的原始条目,它为每个端点上的所有集群授予节点ID 112233(默认控制节点ID)的管理权限。
条目2:这是正在添加的新条目,它向每个端点上的OTA Provider集群(0x0029)的所有节点授予操作特权。
在上面的示例中,提供程序位于fabric索引1上,提供程序节点ID为端点0上的0xDEADBEEF。
(5) 设置请求者(requestor)
如果列表中没有您的提供者,请使用下面的命令写入请求者的default-otaproviders列表。

./out/debug/chip-tool otasoftwareupdaterequestor write default-otaproviders '[{"fabricIndex": 1, "providerNodeID": <PROVIDER_NODE_ID_1>, "endpoint": 0}, {"fabricIndex": 1, "providerNodeID": <PROVIDER_NODE_ID_2>, "endpoint": 0}]' <REQUESTOR_NODE_ID> 0

例如:

otasoftwareupdaterequestor write default-otaproviders '[{"providerNodeID": 3735928559, "endpoint": 0, "fabricIndex": 1}]' 0x7283 0

(6) 启动升级
在进行升级之前,确保电脑的防火墙已经关闭,否则esp32h2接收不到ota数据。调试成功后,使用chip-tool宣布OTA供应商的存在。收到此命令后,OTA请求者将查询OTA图像。

./out/debug/chip-tool otasoftwareupdaterequestor announce-otaprovider <PROVIDER NODE ID> 0 0 0 <REQUESTOR NODE ID> 0

例如:

otasoftwareupdaterequestor announce-otaprovider 3735928559 0 0 0 0x7283 0

传输完成后,OTA请求者(requestor )向OTA提供程序(provider )发送ApplyUpdateRequest命令以应用图像。成功应用OTA映像后,设备将重新启动。
(7) 升级过程中断
使用该方法进行OTA升级,实测传输1.7MB的文件大概需要七八分钟的时间,与网络质量和设备之间的距离有关,实际升级时,WIFI源与边界路由器(HomePod)间隔大概1m,与电脑和esp32h2间隔大概2m,边界路由器(HomePod)在WIFI源和电脑之间。经常会出现升级过程中断,显示已经超过最大传输次数,将电脑和esp32h2放到WIFI源旁边后,升级成功率大大提高。

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐