Skip to content

做一个集大成的demo:Espressif's Matter Demo (youtube.com)

Matter:Clusters, Attributes, Commands

允许基于 802.15.4 的传感器直接打开基于 Wi-Fi 的灯泡,而无需应用程序或云进入画面。称为边界路由器的设备(包括 Wi-Fi 和 802.15.4 传输)有助于桥接这两个网络,从而实现从一个网络到另一个网络的直接寻址性。

为了更好地理解,让我们考虑我们正在构建一个具有 2 个灯的灯具:一个可调光,一个简单开/关。

节点(Node):在我们的例子中,灯具是一个节点。这是一个公开某些功能的唯一网络可寻址实体。这通常是用户可以识别为整个设备的物理设备。

终节点(Endpoint):每个节点都有多个终节点。节点可以看作是一个虚拟设备,它提供的服务可以在逻辑上组合在一起。在上面的示例中,我们的灯具有 2 个独立的灯,一个可调光,一个开关。

请注意,终节点 0 是保留的。这包含适用于整个节点的某些服务。提供有关节点的基本信息,如固件版本、制造商等;允许配置此节点的访问控制列表。

集群(Clusters):集群将常用功能组合到可重用的构建基块中。在我们的图中,我们的第一盏灯(端点 1)显示了 2 个标准集群,即开/关集群和Level Control集群。开/关集群提供打开或关闭某些事物的服务。Level Control 群集提供一项服务,用于配置某些事物的级别。在我们的例子中,开/关集群帮助打开或关闭灯,Level Control集群帮助配置灯光的亮度。

从图中可以看出,集群包含属性和命令。

属性:属性表示可以读取或写入的内容。在我们的示例中,OnOff 群集具有映射到设备实际状态的 OnOff 属性。

On/Off 集群有一个 Toggle 命令,用于切换集群的当前 On/Off 属性。Level Control 群集具有 MoveToLevel、Move、Step 等命令,这些命令以指定方式移动群集的当前级别。

在Matter协议中,设备上的服务器Server提供具体的功能服务,而客户端Client则是用户或其他设备用于控制和交互的入口。客户端Client通过与服务器Server的通信来发送控制命令和接收状态反馈。

Matter: Device-to-Device Automations

1.     Synchronous Control 同步控制

2.     Asynchronous Notification (Subscribe-Report) 异步通知(订阅报告)

用户将恒温器绑定到occupancy sensor。完成后,恒温器可以订阅传感器属性并定期接收数据,以及在有活动(传感器属性更改)时接收数据。此方案如下图所示:

Matter: Bridge for Non-Matter Devices

Bridge 用于允许在 Matter 生态系统 (Matter Fabric) 中使用非 Matter 物联网设备。它使消费者能够继续将这些非 Matter 设备与他们的 Matter 设备一起使用。

下面是 Matter-Zigbee 桥接的示例,它将两个 Zigbee 灯桥接到 Matter 生态系统:

下面是一个 Matter Bridge 设备的数据模型示例

在终结点 0 上,设备类型定义为 Bridge。PartsList 字段列出了桥接设备的所有端点,每个端点代表桥接的非 Matter 端的一个设备。每个端点上的 Descriptor 集群提供有关特定桥接设备的信息。

网桥还可能包含原生 Matter 功能,例如,它本身可能是一个同时具有 Wi-Fi 和 Zigbee 连接的智能恒温器。

步骤1。桥接器是 Matter 中定义的一种设备类型,应遵循标准 Matter 调试流程加入 Matter 结构。

步骤2。Matter-Zigbee 桥接设备也应该加入 Zigbee 网络。与 Matter 略有不同,Zigbee 规范没有强制要求任何标准调试流程,它由设备供应商决定分发链路密钥的工作流程。

步骤3。一旦桥接设备加入 Zigbee 网络,它将通过广播 Match Descriptor Request 命令来发现 Zigbee 网络中支持的设备。该命令包括所需的配置文件、集群内和集群外。在此示例中,它将询问类似“谁拥有带有 OnOff 集群的开/关灯?相应的 Zigbee 设备将回复包含其网络地址的匹配描述符响应。对于每个匹配的 Zigbee Light,桥接器将在 Matter 中添加一个动态端点,它代表桥接 Zigbee 设备。

步骤4。桥接器将所有桥接设备暴露给 Matter 结构,该结构遵循 Matter 规范定义的操作发现方法。

步骤5。现在,Matter 结构中的控制器可以在 Bridge 的帮助下控制 Zigbee 网络中的灯光。

Matter: Thread Border Router in Matter

Matter 使用 Internet 协议 (IP) 定义了一个通用应用层,无论底层网络协议如何,它都能在设备之间提供互操作性。在发布时,Matter 将在以太网、Wi-Fi 和 Thread 上运行。

Thread 是一种基于 IPv6 的低功耗网状网络协议,适用于物联网 (IoT) 产品。它基于 IEEE-802.15.4 技术构建,因此 Thread 设备无法直接与 Wi-Fi 或以太网设备通信。

Let's do it

准备的环境

奥德赛 Ubuntu22.04

idf v5.2.1

matter release/v1.1

操作步骤

2. Developing with the SDK - ESP32 -  — Espressif's SDK for Matter latest documentation

text
git clone --recursive https://github.com/espressif/esp-idf.git
cd esp-idf
git checkout v5.2.1
git submodule update --init --recursive
./install.sh
source ./export.sh
cd ..

connectedhomeip/docs/guides/BUILDING.md at v1.0.0.2 · espressif/connectedhomeip (github.com)

(先把依赖装好了)

bash
sudo apt-get install git gcc g++ pkg-config libssl-dev libdbus-1-dev \
     libglib2.0-dev libavahi-client-dev ninja-build python3-venv python3-dev \
     python3-pip unzip libgirepository1.0-dev libcairo2-dev libreadline-dev
git clone --depth 1 https://github.com/espressif/esp-matter.git
cd esp-matter
git submodule update --init --depth 1
cd ./connectedhomeip/connectedhomeip
./scripts/checkout_submodules.py --platform esp32 linux --shallow
cd ../..
./install.sh
source ./export.sh
cd ..

遇到问题:

在 MacOS 上的 VSCode 上安装 ESP-IDF Matter 扩展时遇到问题 - ESP32 论坛 --- ESP-IDF Matter extension trouble installing on VSCode on MacOS - ESP32 Forum

假设你的Ubuntu用户名是"yourname",esp-idf和esp-matter都位于桌面上,那么你应该在.bashrc文件的末尾添加以下内容:

bash
cd /home/yourname/Desktop/esp-idf; source ./export.sh; cd ..
cd /home/yourname/Desktop/esp-matter; source ./export.sh; cd ..
export IDF_CCACHE_ENABLE=1

请确保将"yourname"替换为你的实际用户名。

完整的步骤如下:

1. 打开终端。

2. 在终端中输入以下命令,打开.bashrc文件:

text
nano ~/.bashrc

3. 在.bashrc文件的末尾添加以下内容:

text
alias get_idf='. /home/yourname/Desktop/esp-idf/export.sh'
alias get_matter='. /home/yourname/Desktop/esp-matter/export.sh'
alias set_cache='export IDF_CCACHE_ENABLE=1'

4. 按下Ctrl + X,然后按Y,最后按Enter以保存更改并关闭文件。

5. 在终端中输入以下命令以使更改生效:

bash
source ~/.bashrc

这样,每次你打开一个新的终端,它都会自动导航到桌面上的esp-idf和esp-matter目录,运行export.sh脚本,并启用Ccache以加速IDF的构建。

如果遇到了错误,例如找不到模块click之类的,需要重新检查虚拟环境的安装:

text
rm -rf ~/esp-idf
rm -rf ~/esp-matter
rm -rf ~/.espressif
idf_tools.py install-python-env

执行light的demo,执行下面的操作:

bash
cd examples/light
idf.py clean
idf.py set-target esp32c6
idf.py menuconfig

配网

因为使用的是UAB——JTAG,所以需要开启 CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG

text
idf.py build
ls /dev/ttyACM*
sudo chmod 666 /dev/ttyACM1
idf.py -p /dev/ttyACM1 flash monitor

matter命令

获得设备的详细细节信息

text
matter config

配网方法:

text
matter esp wifi connect <ssid> <password>

chip-tool调试

v1.0.0.2 的 connectedhomeip/examples/chip-tool ·乐鑫/ConnectedHomeIP --- connectedhomeip/examples/chip-tool at v1.0.0.2 · espressif/connectedhomeip (github.com)

下面的命令将发现设备,并尝试使用提供的设置代码与它发现的第一个设备配对。

c
chip-tool pairing onnetwork ${NODE_ID_TO_ASSIGN} 20202021

下面的命令将发现具有长鉴别器 3840 的设备,并尝试使用提供的设置代码与它发现的第一个设备配对。

c
chip-tool pairing onnetwork-long ${NODE_ID_TO_ASSIGN} 20202021 3840

下面的命令将根据给定的二维码(设备启动时记录的设备)发现设备,并尝试与它发现的第一个设备配对。

c
chip-tool pairing code ${NODE_ID_TO_ASSIGN} MT:#######

在所有这些情况下,将为设备分配节点 ID ${NODE_ID_TO_ASSIGN} (必须是十进制数或以 0x 为前缀的十六进制数)。

忘记当前调试的设备:

c
chip-tool pairing unpair ${NODE_ID_TO_ASSIGN}

要使用客户端发送 Matter 命令,请运行构建的可执行文件并向其传递目标集群名称、目标命令名称以及端点 ID。

c
chip-tool onoff on/off ${NODE_ID_TO_ASSIGN} 0x1

生成Matter二维码:

A1 附录常见问题 - ESP32 - — 乐鑫 SDK for Matter 最新文档 --- A1 Appendix FAQs - ESP32 - — Espressif's SDK for Matter latest documentation

通过matter config命令查询下面的信息,可以通过使用chip-tool来生成二维码

text
chip-tool payload generate-manualcode --discriminator 3840 --setup-pin-code 20202021 \
                                      --version 0 --commissioning-mode 0

对于matter命令,执行下面的命令,可以生成二维码的链接:

text
matter onboardingcodes onnetwork

有个好的串口监视器太重要了

bash
sudo apt install minicom

设置minicom

bash
sudo minicom -s

使用minicom

bash
sudo minicom

常用的minicom的命令:首先按下并按住Ctrl键。

在按住Ctrl键的同时,按下A键。

松开Ctrl和A键。

按下Z等键。

text
Ctrl+A Z: 打开Minicom的帮助菜单。
Ctrl+A C: 清除屏幕。
Ctrl+A L: 刷新屏幕。
Ctrl+A X: 退出Minicom。
Ctrl+A S: 发送文件。
Ctrl+A R: 接收文件。

串口的权限问题:

  1. 检查端口的组权限:

在终端中输入以下命令:

bash
ls -l /dev/ttyACM*
  • 这将显示拥有对端口访问权限的组。

  • 例如,如果输出是crw-rw-r-- 1 root dialout ... /dev/ttyACM0,则root是所有者,dialout是拥有访问权限的组。

  1. 检查你所属的组:

在终端中输入以下命令:

text
groups
  • 这将显示你所属的用户组列表。

  • 将你的用户添加到拥有访问权限的组(如果需要):

  1. 如果你不在拥有访问权限的组中,可以使用以下命令将自己添加到该组:
bash
sudo adduser YourUserName GroupToJoin
  • 将YourUserName替换为你的实际用户名,将GroupToJoin替换为你需要加入的组名(例如dialout)。

  • 输入密码并确认操作。

开发框架概述(以light为例)

Matter Development Framework Overview | Seeed Studio Wiki

example/common/app_reset 中,包含按键长按重置的代码,建议复制使用

components/esp_matter中,涵盖了matter的相关头文件,属性、群集等等

设备初始化

1.     初始化NVS

2.     初始化硬件驱动(app_driver.cpp)

灯的:

c
led_indicator_handle_t leds[CONFIG_BSP_LEDS_NUM];  //句柄都在esp-iot-solution的开发框架下面
ESP_ERROR_CHECK(bsp_led_indicator_create(leds, NULL, CONFIG_BSP_LEDS_NUM));
led_indicator_set_hsv(leds[0], SET_HSV(DEFAULT_HUE, DEFAULT_SATURATION, DEFAULT_BRIGHTNESS));
return (app_driver_handle_t)leds[0];

按键的:

c
/* Initialize button */
button_handle_t btns[BSP_BUTTON_NUM];
ESP_ERROR_CHECK(bsp_iot_button_create(btns, NULL, BSP_BUTTON_NUM));
// 调用 iot_button_register_cb 函数为第一个按钮 (即 btns[0]) 注册一个回调函数 app_driver_button_toggle_cb
// 这个回调函数会在按钮被按下 (BUTTON_PRESS_DOWN 事件) 时被调用。NULL 参数表示不向回调函数传递任何用户数据。
ESP_ERROR_CHECK(iot_button_register_cb(btns[0], BUTTON_PRESS_DOWN, app_driver_button_toggle_cb, NULL));
return (app_driver_handle_t)btns[0];
c
// 按键的回调函数:当关联的按钮被按下时,获取当前灯的开关状态,将其切换到相反状态,并将新的状态值发送到设备,从而实现灯的开关控制。
static void app_driver_button_toggle_cb(void *arg, void *data)
{
    ESP_LOGI(TAG, "Toggle button pressed");
    uint16_t endpoint_id = light_endpoint_id;   // 属于是endpoint0
    uint32_t cluster_id = OnOff::Id;   // 开关集群
    uint32_t attribute_id = OnOff::Attributes::OnOff::Id;   // 开关属性

    node_t *node = node::get();
    endpoint_t *endpoint = endpoint::get(node, endpoint_id);
    cluster_t *cluster = cluster::get(endpoint, cluster_id);
    attribute_t *attribute = attribute::get(cluster, attribute_id);

    esp_matter_attr_val_t val = esp_matter_invalid(NULL);
    attribute::get_val(attribute, &val);
    val.val.b = !val.val.b;
    attribute::update(endpoint_id, cluster_id, attribute_id, &val);
}

创建Matter node

通常,数据模型在示例的app_main.cpp中定义。首先,我们首先创建一个 Matter 节点,它是数据模型的根。Matter Node 代表 Matter 生态系统中的物理设备或逻辑实体。它是 Matter 数据模型的顶层组件。每个 Matter Node 都有一个唯一的标识符,并且可以包含一个或多个 Endpoints。

c
node::config_t node_config;

// node handle can be used to add/modify other endpoints.
node_t *node = node::create(&node_config, app_attribute_update_cb, app_identification_cb);
ABORT_APP_ON_FAILURE(node != nullptr, ESP_LOGE(TAG, "Failed to create Matter node"));

在这种情况下,color_temperature_light 我们将使用标准设备类型。所有标准设备类型都在 esp_matter_endpoint.h 头文件中提供。每种设备类型都有一组默认配置,这些配置也可以是特定的。

  • Matter 节点代表 Matter 生态系统中的物理设备。

  • 它就像一座可以包含多个端点(房间)的房子。

  • 每个物质节点都有自己唯一的标识符,用于在网络内进行识别和寻址。

设置Endpoint的属性

创建 Matter Node 后,需要为 Endpoints 的属性设置默认值。

c
extended_color_light::config_t light_config;
light_config.on_off.on_off = DEFAULT_POWER;
light_config.on_off.lighting.start_up_on_off = nullptr;
light_config.level_control.current_level = DEFAULT_BRIGHTNESS;
light_config.level_control.lighting.start_up_current_level = DEFAULT_BRIGHTNESS;
light_config.color_control.color_mode = (uint8_t)ColorControl::ColorMode::kColorTemperature;
light_config.color_control.enhanced_color_mode = (uint8_t)ColorControl::ColorMode::kColorTemperature;
light_config.color_control.color_temperature.startup_color_temperature_mireds = nullptr;

Matter 中的属性就像设备的属性或特征。它们存储有关设备状态的信息,例如设备是否打开或关闭、亮度级别或色温。这些属性被组织成称为簇的组,它们与设备的特定功能相关。属性使不同设备和应用程序能够更轻松地无缝通信和协作。

esp_matter_endpoint.h 是 ESP Matter SDK 中的重要头文件,定义了与端点相关的常量、数据类型和函数。

c
namespace extended_color_light {
typedef struct config {
    cluster::descriptor::config_t descriptor;
    cluster::identify::config_t identify;
    cluster::groups::config_t groups;
    cluster::scenes_management::config_t scenes_management;
    cluster::on_off::config_t on_off;
    cluster::level_control::config_t level_control;
    cluster::color_control::config_t color_control;
} config_t;

uint32_t get_device_type_id();
uint8_t get_device_type_version();
endpoint_t *create(node_t *node, config_t *config, uint8_t flags, void *priv_data);
esp_err_t add(endpoint_t *endpoint, config_t *config);
} /* extended_color_light */

在 Matter 中,端点代表设备的逻辑接口,每个端点包含一组描述和控制设备特定功能的属性和命令。

创建endpoint和自动匹配cluster

Endpoint(s) [Device-Type(s)]:端点是事务节点内特定功能或服务的逻辑表示。它封装了一组与特定设备类型相关的功能和行为。一个 Matter Node 可以有多个 Endpoint,每个 Endpoint 代表不同的设备类型。设备类型定义端点的特定特征和功能。 Matter 定义了一组标准设备类型,例如灯泡、恒温器、门锁等。每种设备类型都有一个唯一的标识符以及一组预定义的与其关联的集群、属性和命令。

  • 端点是事务节点内的逻辑组件,代表设备的特定功能或服务。

  • 就像房子里的房间一样,每个端点都有自己的专用用途,例如卧室、厨房或客厅。

  • 每个端点都与特定的设备类型相关联,例如灯泡、恒温器或门锁。

  • 一个 Matter Node 可以有多个 Endpoint,每个 Endpoint 代表不同的设备类型和功能。

Cluster(s):集群是端点内相关属性和命令的逻辑分组。它们代表设备的特定功能或特性。集群提供了一种组织和分类端点功能的方法。例如,“开/关集群”包含与打开或关闭设备相关的属性和命令,而“级别控制集群”则涉及控制设备的亮度或级别。

  • 集群是端点内的逻辑分组,包含相关属性和命令。

  • 就像房间里的家具或设备,如灯、电视或空调,每个都有自己的属性和操作。

  • 每个集群代表设备的特定功能或特性。

  • 例如,“开/关簇”包含与设备的开/关状态相关的属性和命令,而“电平控制簇”包含用于调整设备的亮度或电平的属性和命令。

  • 一个端点可以有多个集群,每个集群负责不同的功能。

综上所述,一个 Matter Node 就像一座房子,包含多个 Endpoints(房间)。每个端点就像一个房间,代表设备的特定功能或服务。集群就像每个房间中的家具或设备,包含相关的属性以及用于控制和交互的命令。这种分层组织允许设备清晰地描述其功能和特性,使应用程序和其他设备更容易与它们交互和控制它们。

在代码中,设置属性后,最终通过以下代码片段创建了一个Endpoint。并且它会自动匹配所设置属性的Cluster。

c
endpoint_t *endpoint = extended_color_light::create(node, &light_config, ENDPOINT_FLAG_NONE, light_handle);
ABORT_APP_ON_FAILURE(endpoint != nullptr, ESP_LOGE(TAG, "Failed to create extended color light endpoint"));

自动匹配聚类是如何实现的?让我们举一个设置属性的代码片段的示例。

light_config.level_control 是 Endpoint (esp_matter_endpoint.h) 中定义的属性。 light_config.level_control.lighting 是 Cluster (esp_matter_cluster) 中定义的属性。通过这样的设置,系统可以自动匹配该Attribute对应的Cluster,而不需要开发者手动设置。

首次使用默认值设置 Matter 设备

配置完上述属性、集群和端点后,我们就可以开始启动 Matter 设备了。启动步骤和方法如下。

c
light_endpoint_id = endpoint::get_id(endpoint);
ESP_LOGI(TAG, "Light created with endpoint_id %d", light_endpoint_id);

/* Matter start */
err = esp_matter::start(app_event_cb);
ABORT_APP_ON_FAILURE(err == ESP_OK, ESP_LOGE(TAG, "Failed to start Matter, err:%d", err));

/* Starting driver with default values 使用默认值启动驱动程序 */
app_driver_light_set_defaults(light_endpoint_id);

如您所见,设置默认值的函数是 app_driver_light_set_defaults() ,我们需要传入端点 ID 作为参数。而我们需要关心如何获取某个簇、某个属性的值,以及如何设置默认的簇、属性值。秘密显示在 app_driver.cpp 中。

c
esp_err_t err = ESP_OK;
void *priv_data = endpoint::get_priv_data(endpoint_id);
led_indicator_handle_t handle = (led_indicator_handle_t)priv_data;
node_t *node = node::get();
endpoint_t *endpoint = endpoint::get(node, endpoint_id);
cluster_t *cluster = NULL;
attribute_t *attribute = NULL;
esp_matter_attr_val_t val = esp_matter_invalid(NULL);

/* Setting brightness */
cluster = cluster::get(endpoint, LevelControl::Id);
attribute = attribute::get(cluster, LevelControl::Attributes::CurrentLevel::Id);
attribute::get_val(attribute, &val);
err |= app_driver_light_set_brightness(handle, &val);

通过执行以下步骤,您可以获得指向集群和属性的必要指针,检索属性的当前值,并相应地设置灯光的亮度。

使用 endpoint::get(node, endpoint_id) 获取端点指针。

使用 cluster::get(endpoint, LevelControl::Id) 获取簇指针。

使用 attribute::get(cluster, LevelControl::Attributes::CurrentLevel::Id) 获取属性指针。

使用 attribute::get_val(attribute, &val) 检索属性的当前值。

使用 app_driver_light_set_brightness(handle, &val) 设置灯光的亮度,其中 handle 是与端点关联的 LED 指示灯句柄。

数据更新和延迟持久性

在 app_driver.cpp 的代码中,使用 app_driver_attribute_update() 函数更新属性的值。

c
if (endpoint_id == light_endpoint_id) {
   led_indicator_handle_t handle = (led_indicator_handle_t)driver_handle;
   if (cluster_id == OnOff::Id) {
      if (attribute_id == OnOff::Attributes::OnOff::Id) {
            err = app_driver_light_set_power(handle, val);
      }
   } else if (cluster_id == LevelControl::Id) {
      if (attribute_id == LevelControl::Attributes::CurrentLevel::Id) {
            err = app_driver_light_set_brightness(handle, val);
      }
   } else if (cluster_id == ColorControl::Id) {
      if (attribute_id == ColorControl::Attributes::CurrentHue::Id) {
            err = app_driver_light_set_hue(handle, val);
      } else if (attribute_id == ColorControl::Attributes::CurrentSaturation::Id) {
            err = app_driver_light_set_saturation(handle, val);
      } else if (attribute_id == ColorControl::Attributes::ColorTemperatureMireds::Id) {
            err = app_driver_light_set_temperature(handle, val);
      }
   }
}
  1. 该函数首先检查 endpoint_id 是否与 light_endpoint_id 匹配。这可确保更新适用于轻端点。

  2. 如果 endpoint_id 匹配,该函数会将 driver_handle 转换为适当的类型 ( led_indicator_handle_t ),以获取与灯光端点关联的 LED 指示器的句柄。

  3. 然后该函数检查 cluster_id 以确定该属性属于哪个簇。它支持三个集群: OnOff 、 LevelControl 和 ColorControl 。

  4. 根据 cluster_id ,该函数进一步检查 attribute_id 以识别该集群中的特定属性。

  5. 该函数根据 cluster_id 和 attribute_id 调用相应的 setter 函数来更新属性值:

  • 如果 cluster_id 是 OnOff::Id 并且 attribute_id 是 OnOff::Attributes::OnOff::Id ,它调用 app_driver_light_set_power(handle, val) 来设置电源状态光。

  • 如果 cluster_id 是 LevelControl::Id 并且 attribute_id 是 LevelControl::Attributes::CurrentLevel::Id ,它调用 app_driver_light_set_brightness(handle, val) 来设置亮度级别光。

  • 如果 cluster_id 是 ColorControl::Id ,它会进一步检查 attribute_id :

    • 如果 attribute_id 是 ColorControl::Attributes::CurrentHue::Id ,它会调用 app_driver_light_set_hue(handle, val) 来设置灯光的色调。

    • 如果 attribute_id 是 ColorControl::Attributes::CurrentSaturation::Id ,它会调用 app_driver_light_set_saturation(handle, val) 来设置灯光的饱和度。

    • 如果 attribute_id 是 ColorControl::Attributes::ColorTemperatureMireds::Id ,它会调用 app_driver_light_set_temperature(handle, val) 来设置灯光的色温。

但并非所有属性都会实时更新。将那些可能经常更改的属性标记为延迟持久性的代码可以提高性能,并减少对非易失性内存的写入次数并延长设备的使用寿命。

定义自己的数据模型

Endpoint

可以通过在示例app_main.cpp中编辑 endpoint/device_type 创建来自定义设备。例子:

on_off_light:

c
on_off_light::config_t light_config;
endpoint_t *endpoint = on_off_light::create(node, &light_config, ENDPOINT_FLAG_NONE);

fan:

c
fan::config_t fan_config;
endpoint_t *endpoint = fan::create(node, &fan_config, ENDPOINT_FLAG_NONE);

door_lock:

c
door_lock::config_t door_lock_config;
endpoint_t *endpoint = door_lock::create(node, &door_lock_config, ENDPOINT_FLAG_NONE);

window_covering_device:

c
window_covering_device::config_t window_covering_device_config(static_cast<uint8_t>(chip::app::Clusters::WindowCovering::EndProductType::kTiltOnlyInteriorBlind));
endpoint_t *endpoint = window_covering_device::create(node, &window_covering_config, ENDPOINT_FLAG_NONE);

Clusters

on_off:

c
on_off::config_t on_off_config;
cluster_t *cluster = on_off::create(endpoint, &on_off_config, CLUSTER_FLAG_SERVER, on_off::feature::lighting::get_id());

temperature_measurement:

c
temperature_measurement::config_t temperature_measurement_config;
cluster_t *cluster = temperature_measurement::create(endpoint, &temperature_measurement_config, CLUSTER_FLAG_SERVER);

window_covering:

c
window_covering::config_t window_covering_config(static_cast<uint8_t>(chip::app::Clusters::WindowCovering::EndProductType::kTiltOnlyInteriorBlind));
cluster_t *cluster = window_covering::create(endpoint, &window_covering_config, CLUSTER_FLAG_SERVER);

pump_configuration_and_control:

c
pump_configuration_and_control::config_t pump_configuration_and_control_config(1, 10, 20);
cluster_t *cluster = pump_configuration_and_control::create(endpoint, &pump_configuration_and_control_config, CLUSTER_FLAG_SERVER);

属性和命令

添加额外的属性到集群

attribute: on_off:

c
bool default_on_off = true;
attribute_t *attribute = on_off::attribute::create_on_off(cluster, default_on_off);

attribute: cluster_revision:

c
uint16_t default_cluster_revision = 1;
attribute_t *attribute = global::attribute::create_cluster_revision(cluster, default_cluster_revision);

command: toggle:

text
command_t *command = on_off::command::create_toggle(cluster);

command: move_to_level:

text
command_t *command = level_control::command::create_move_to_level(cluster);

功能

还可以添加适用于群集的可选功能

feature: taglist: Descriptor cluster:

c
cluster_t* cluster = cluster::get(endpoint, Descriptor::Id);
descriptor::feature::taglist::add(cluster);

添加自定义数据模型字段

创建非标准节点,没有集群

c
endpoint_t *endpoint = endpoint::create(node, ENDPOINT_FLAG_NONE);

创建非标准/自定义集群

c
uint32_t custom_cluster_id = 0x131bfc00;
cluster_t *cluster = cluster::create(endpoint, custom_cluster_id, CLUSTER_FLAG_SERVER);

还可以在任何集群上创建非标准/自定义属性

属性创建

c
uint32_t custom_attribute_id = 0x0;
uint16_t default_value = 100;
attribute_t *attribute = attribute::create(cluster, custom_attribute_id, ATTRIBUTE_FLAG_NONE, esp_matter_uint16(default_value);

命令创建

js
static esp_err_t command_callback(const ConcreteCommandPath &command_path, TLVReader &tlv_data, void
*opaque_ptr)
{
   ESP_LOGI(TAG, "Custom command callback");
   return ESP_OK;
}

uint32_t custom_command_id = 0x0;
command_t *command = command::create(cluster, custom_command_id, COMMAND_FLAG_ACCEPTED, command_callback);

API

Endpoint/Device Type:components/esp_matter/esp_matter_endpoint.h

Cluster:components/esp_matter/esp_matter_cluster.h

Attribute:components/esp_matter/esp_matter_attribute.h

Command:components/esp_matter/esp_matter_command.h

Core Low Level:components/esp_matter/esp_matter_core.h

Event:components/esp_matter/esp_matter_event.h

Client:components/esp_matter/esp_matter_client.h

Zigbee Arduino

Node 代表 Zigbee 网络中的网络节点。单个节点可以公开多个端点Endpoints。

Endpoints

每个node内都有endpoints。端点由 1 到 240 之间的数字标识,用于定义在 ZigBee node中运行的每个应用程序(是的,单个 ZigBee node可以运行多个应用程序)。Endpoints在ZigBee中有三个用途:

  • Endpoint允许每个node内存在不同的应用程序配置文件

  • Endpoint允许每个node内存在单独的控制点

  • Endpoint允许每个node内存在单独的设备

Clusters

由 16 位标识符定义的 cluster 是应用对象。NwkAddr 和端点是寻址概念,而 cluster 则定义了应用程序的含义。

  • 一个 endpont 可以有多个 cluster

  • cluster 除了标识符之外,还有方向。在描述 endpoint 的 SimpleDescriptor 中,集群被列为输入或输出

  • 集群包含code(command)和data(attributes)。command控制action。attribute跟踪该cluster的当前状态。

Zigbee_Light_Bulb(zigbee ED)

arduino-esp32/libraries/ESP32/examples/Zigbee/Zigbee_Light_Bulb at master · espressif/arduino-esp32 (github.com)

初始化

c
#define ESP_ZB_DEFAULT_RADIO_CONFIG() \
  { .radio_mode = ZB_RADIO_MODE_NATIVE, }

#define ESP_ZB_DEFAULT_HOST_CONFIG() \
  { .host_connection_mode = ZB_HOST_CONNECTION_MODE_NONE, }

void setup() {
  // Init Zigbee 设置默认的无线电和主机配置
  esp_zb_platform_config_t config = {
    .radio_config = ESP_ZB_DEFAULT_RADIO_CONFIG(),
    .host_config = ESP_ZB_DEFAULT_HOST_CONFIG(),
  };
  ESP_ERROR_CHECK(esp_zb_platform_config(&config));
}

创建Zigbee RTOS任务

c
static void esp_zb_task(void *pvParameters) {
  esp_zb_cfg_t zb_nwk_cfg = ESP_ZB_ZED_CONFIG();        // 设置Zigbee终端设备(ZED)的默认配置
  esp_zb_init(&zb_nwk_cfg);                             // 初始化Zigbee协议栈
  esp_zb_on_off_light_cfg_t light_cfg = ESP_ZB_DEFAULT_ON_OFF_LIGHT_CONFIG();  // 设置On/Off灯的默认配置
  esp_zb_ep_list_t *esp_zb_on_off_light_ep = esp_zb_on_off_light_ep_create(HA_ESP_LIGHT_ENDPOINT, &light_cfg);  // 创建一个On/Off灯的Zigbee端点
  esp_zb_device_register(esp_zb_on_off_light_ep);  // 向Zigbee协议栈注册设备
  esp_zb_core_action_handler_register(zb_action_handler);  // 将zb_action_handler函数注册为Zigbee动作的处理程序
  esp_zb_set_primary_network_channel_set(ESP_ZB_PRIMARY_CHANNEL_MASK);  // 设置Zigbee网络的主信道集

  //Erase NVRAM before creating connection to new Coordinator 如果需要在连接到新的Zigbee协调器时擦除NVRAM数据,可以取消注释
  //esp_zb_nvram_erase_at_start(true); //Comment out this line to erase NVRAM data if you are conneting to new Coordinator

  ESP_ERROR_CHECK(esp_zb_start(false));  // 启动Zigbee协议栈
  esp_zb_main_loop_iteration();  // 运行Zigbee协议栈的主循环
}

void setup() {
  // Start Zigbee task
  xTaskCreate(esp_zb_task, "Zigbee_main", 4096, NULL, 5, NULL);
}

esp_ze_cfg_t的结构体在esp_zigbee_core.h中定义:

c
/**
 * @brief Zigbee 设备配置。
 * @note  关于 esp_zb_role,请参考 esp_zb_nwk_device_type_t 的定义。
 */
typedef struct esp_zb_cfg_s {
    esp_zb_nwk_device_type_t esp_zb_role; /*!< The nwk device type */
    bool install_code_policy;             /*!< Allow install code security policy or not */
    union {
        esp_zb_zczr_cfg_t zczr_cfg; /*!< Zigbee协调器/路由器设备配置 */
        esp_zb_zed_cfg_t zed_cfg;   /*!< Zigbee终端设备配置 */
    } nwk_cfg;                      /*!< Union of the network configuration */
} esp_zb_cfg_t;

这决定了ESP_ZB_ZED_CONFIG函数的写法:

c
/* Default End Device config */
// 定义了一个宏函数ESP_ZB_ZED_CONFIG(),用于生成Zigbee终端设备(End Device, ED)的默认配置
#define ESP_ZB_ZED_CONFIG()                                                                 \
  {                                                                                         \
    .esp_zb_role = ESP_ZB_DEVICE_TYPE_ED,                                                   \
    .install_code_policy = INSTALLCODE_POLICY_ENABLE,                                       \
    .nwk_cfg = {                                                                            \
      .zed_cfg =                                                                            \
        {                                                                                   \
          .ed_timeout = ED_AGING_TIMEOUT,                                                   \
          .keep_alive = ED_KEEP_ALIVE,                                                      \
        },                                                                                  \
    },                                                                                      \
  }

Zigbee设备的RTOS任务的一般流程和步骤如下:

1. 配置Zigbee设备:

- 设置Zigbee设备的类型和默认配置,如终端设备(ZED)的默认配置esp_zb_cfg_t。

- 如果需要,可以自定义配置参数。自己写ESP_ZB_ZED_CONFIG()函数。

2. 初始化Zigbee协议栈:

- 调用esp_zb_init函数,传入配置参数,初始化Zigbee协议栈。

3. 创建和配置Zigbee端点(Endpoint):

- 根据设备的功能,创建一个或多个Zigbee端点。

- 设置端点的默认配置,如创建标准的单 HA 颜色可调光endpoint的默认配置esp_zb_color_dimmable_light_ep_create。

- 调用相应的函数(如esp_zb_color_dimmable_light_ep_create)创建端点,并指定端点的ID和配置参数。

4. 向Zigbee协议栈注册设备:

- 调用esp_zb_device_register函数,将创建的端点注册到Zigbee协议栈中。

5. 注册Zigbee动作的处理程序:

- 调用esp_zb_core_action_handler_register函数,将自定义的处理函数(如zb_action_handler)注册为Zigbee动作的处理程序。

6. 设置Zigbee网络的主信道集:

- 调用esp_zb_set_primary_network_channel_set函数,设置Zigbee网络的主信道集。

7. (可选)擦除NVRAM数据:

- 如果需要在连接到新的Zigbee协调器时擦除NVRAM数据,可以调用esp_zb_nvram_erase_at_start函数。

8. 启动Zigbee协议栈:

- 调用esp_zb_start函数,传入适当的参数,启动Zigbee协议栈。

9. 运行Zigbee协议栈的主循环:

- 调用esp_zb_main_loop_iteration函数,运行Zigbee协议栈的主循环,处理Zigbee事件和消息。

Zigbee动作的处理程序

js
// callback_id 表示触发该回调函数的Zigbee动作的类型。message是一个指向与该动作相关的消息数据的指针
static esp_err_t zb_action_handler(esp_zb_core_action_callback_id_t callback_id, const void *message) {
  esp_err_t ret = ESP_OK;
  switch (callback_id) {
    case ESP_ZB_CORE_SET_ATTR_VALUE_CB_ID:
      ret = zb_attribute_handler((esp_zb_zcl_set_attr_value_message_t *)message);  // 传递给zb_attribute_handler函数进行处理。
      break;
    default:
      log_w("Receive Zigbee action(0x%x) callback", callback_id);  // 收到了一个未知的Zigbee动作回调
      break;
  }
  return ret;
}

动作是根据callback id来决定的,如果callback id为ESP_ZB_CORE_SET_ATTR_VALUE_CB_ID,即为设置属性的动作,则执行zb_attribute_handler函数。

js
/* Handle the light attribute */
// message作为参数,该结构体包含了设置Zigbee属性值的消息数据
static esp_err_t zb_attribute_handler(const esp_zb_zcl_set_attr_value_message_t *message) {
  esp_err_t ret = ESP_OK;
  bool light_state = 0;

  if (!message) {
    log_e("Empty message");
  }
  if (message->info.status != ESP_ZB_ZCL_STATUS_SUCCESS) {
    log_e("Received message: error status(%d)", message->info.status);
  }

  log_i(
    // 目标端点、集群ID、属性ID和属性数据的大小
    "Received message: endpoint(%d), cluster(0x%x), attribute(0x%x), data size(%d)", message->info.dst_endpoint, message->info.cluster, message->attribute.id,
    message->attribute.data.size
  );
  if (message->info.dst_endpoint == HA_ESP_LIGHT_ENDPOINT) {  // 判断消息的目标端点是否等于HA_ESP_LIGHT_ENDPOINT
    if (message->info.cluster == ESP_ZB_ZCL_CLUSTER_ID_ON_OFF) {  // On/Off集群的属性设置消息
      if (message->attribute.id == ESP_ZB_ZCL_ATTR_ON_OFF_ON_OFF_ID && message->attribute.data.type == ESP_ZB_ZCL_ATTR_TYPE_BOOL) {  // 设置灯的开关状态的消息
        light_state = message->attribute.data.value ? *(bool *)message->attribute.data.value : light_state;
        log_i("Light sets to %s", light_state ? "On" : "Off");
        if (light_state) {
          digitalWrite(LED_BUILTIN, LOW);   // Turn on the light
        } else {
          digitalWrite(LED_BUILTIN, HIGH);  // Turn off the light
        }
//        neopixelWrite(LED_PIN, 255 * light_state, 255 * light_state, 255 * light_state);  // Toggle light
      }
    }
  }
  return ret;
}

这个函数的主要作用是处理设置Zigbee On/Off集群中的On/Off属性的消息,根据消息中的属性值来设置连接到LED_PIN的LED灯的状态。如果收到的消息不是针对灯的属性设置消息,或者消息中的属性ID不是On/Off属性,函数不会进行任何实际的处理。

Zigbee堆栈应用信号处理程序

Zigbee 堆栈应用信号处理程序,该函数必须由用户在每个示例中定义。

这个函数的主要作用是处理Zigbee应用程序的各种信号,包括协议栈初始化完成信号、

设备启动/重启信号和网络引导结果信号等,并根据信号的类型和错误状态来执行相应的操作,

如开始调试过程、加入网络、重新开始网络引导等。这个函数是Zigbee应用程序的核心组成部分之一,负责协调应用程序和Zigbee协议栈之间的交互。

c
void esp_zb_app_signal_handler(esp_zb_app_signal_t *signal_struct) {
  uint32_t *p_sg_p = signal_struct->p_app_signal;
  esp_err_t err_status = signal_struct->esp_err_status;
  esp_zb_app_signal_type_t sig_type = (esp_zb_app_signal_type_t)*p_sg_p;
  switch (sig_type) {
    // 表示Zigbee协议栈已经初始化完成,启动顶层调试(TLC)过程,表示设备正在初始化
    case ESP_ZB_ZDO_SIGNAL_SKIP_STARTUP:
      log_i("Zigbee stack initialized");
      esp_zb_bdb_start_top_level_commissioning(ESP_ZB_BDB_MODE_INITIALIZATION);
      break;
    // 表示设备是第一次启动或重启,函数会检查状态err_status是否为ESP_OK,表示初始化成功。
    // 如果初始化成功,函数会输出一条信息日志,表示开始组建网络
    case ESP_ZB_BDB_SIGNAL_DEVICE_FIRST_START:
    case ESP_ZB_BDB_SIGNAL_DEVICE_REBOOT:
      if (err_status == ESP_OK) {
        log_i("Start network steering");  // 启动网络引导
        esp_zb_bdb_start_top_level_commissioning(ESP_ZB_BDB_MODE_NETWORK_STEERING);
      } else {
        /* commissioning failed */
        log_w("Failed to initialize Zigbee stack (status: %s)", esp_err_to_name(err_status));
      }
      break;
    // 表示设备加入网络成功
    case ESP_ZB_BDB_SIGNAL_STEERING:
      if (err_status == ESP_OK) {
        esp_zb_ieee_addr_t extended_pan_id;
        esp_zb_get_extended_pan_id(extended_pan_id);
        log_i(
          "Joined network successfully (Extended PAN ID: %02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x, PAN ID: 0x%04hx, Channel:%d, Short Address: 0x%04hx)",
          extended_pan_id[7], extended_pan_id[6], extended_pan_id[5], extended_pan_id[4], extended_pan_id[3], extended_pan_id[2], extended_pan_id[1],
          extended_pan_id[0], esp_zb_get_pan_id(), esp_zb_get_current_channel(), esp_zb_get_short_address()
        );
      } else {
        log_i("Network steering was not successful (status: %s)", esp_err_to_name(err_status));
        esp_zb_scheduler_alarm((esp_zb_callback_t)bdb_start_top_level_commissioning_cb, ESP_ZB_BDB_MODE_NETWORK_STEERING, 1000);
      }
      break;
    default: log_i("ZDO signal: %s (0x%x), status: %s", esp_zb_zdo_signal_to_string(sig_type), sig_type, esp_err_to_name(err_status)); break;
  }
}

这个信号处理的逻辑其实是顺序地逐个逐个状态逐个判断执行不同的任务。

  • ESP_ZB_ZDO_SIGNAL_SKIP_STARTUP,表示Zigbee协议栈已经初始化完成,函数会输出一条信息日志,然后调用esp_zb_bdb_start_top_level_commissioning函数,并传递ESP_ZB_BDB_MODE_INITIALIZATION参数,表示开始顶级调试 (Top Level Commissioning)过程的初始化阶段。

  • ESP_ZB_BDB_SIGNAL_DEVICE_FIRST_START或ESP_ZB_BDB_SIGNAL_DEVICE_REBOOT,表示设备第一次启动或重启。如果err_status为ESP_OK,函数会输出一条信息日志,然后调用esp_zb_bdb_start_top_level_commissioning函数,并传递ESP_ZB_BDB_MODE_NETWORK_STEERING参数,表示开始网络引导(Network Steering)阶段。

  • 如果sig_type的值为ESP_ZB_BDB_SIGNAL_STEERING,表示网络引导阶段的结果。如果err_status为ESP_OK,表示设备成功加入了Zigbee网络,函数会输出一条信息日志,记录了网络的一些关键信息,包括扩展PAN ID、PAN ID、信道号和设备的短地址。如果err_status不为ESP_OK,表示网络引导失败,函数会输出一条信息日志,然后调用esp_zb_scheduler_alarm函数,传递bdb_start_top_level_commissioning_cb回调函数和ESP_ZB_BDB_MODE_NETWORK_STEERING参数,表示1000毫秒后重新开始网络引导阶段。

Zigbee_Light_Switch(zigbee coordinator)

arduino-esp32/libraries/ESP32/examples/Zigbee/Zigbee_Light_Switch at master · espressif/arduino-esp32 (github.com)

初始化和RTOS任务

跟ED是类似的,只是在RTOS的任务中,少了将某个函数注册为Zigbee的动作处理程序。

所以对于Zigbee解调器来讲,最关键的部分是搜索并匹配对应的设备,以及对设备下发控制命令的部分。

搜索并匹配设备

这一部分是在Zigbee堆栈应用信号处理程序里面完成的:

c
void esp_zb_app_signal_handler(esp_zb_app_signal_t *signal_struct) {
  uint32_t *p_sg_p = signal_struct->p_app_signal;
  esp_err_t err_status = signal_struct->esp_err_status;
  esp_zb_app_signal_type_t sig_type = (esp_zb_app_signal_type_t)*p_sg_p;
  esp_zb_zdo_signal_device_annce_params_t *dev_annce_params = NULL;
  switch (sig_type) {
    // 表示Zigbee协议栈已经初始化完成,启动顶层调试(TLC)过程,表示设备正在初始化
    case ESP_ZB_ZDO_SIGNAL_SKIP_STARTUP:
      log_i("Zigbee stack initialized");
      esp_zb_bdb_start_top_level_commissioning(ESP_ZB_BDB_MODE_INITIALIZATION);
      break;
    // 表示设备是第一次启动或重启,函数会检查状态err_status是否为ESP_OK,表示初始化成功。
    // 如果初始化成功,函数会输出一条信息日志,表示开始组建网络
    case ESP_ZB_BDB_SIGNAL_DEVICE_FIRST_START:
    case ESP_ZB_BDB_SIGNAL_DEVICE_REBOOT:
      if (err_status == ESP_OK) {
        log_i("Start network formation");  // 开始组建网络
        esp_zb_bdb_start_top_level_commissioning(ESP_ZB_BDB_MODE_NETWORK_FORMATION);
      } else {
        log_e("Failed to initialize Zigbee stack (status: %s)", esp_err_to_name(err_status));
      }
      break;
    // 表示网络组建过程已经完成,打印出网络的扩展PAN ID、PAN ID、信道和短地址,设备正在加入网络
    case ESP_ZB_BDB_SIGNAL_FORMATION:
      if (err_status == ESP_OK) {
        esp_zb_ieee_addr_t extended_pan_id;
        esp_zb_get_extended_pan_id(extended_pan_id);
        log_i(
          "Formed network successfully (Extended PAN ID: %02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x, PAN ID: 0x%04hx, Channel:%d, Short Address: 0x%04hx)",
          extended_pan_id[7], extended_pan_id[6], extended_pan_id[5], extended_pan_id[4], extended_pan_id[3], extended_pan_id[2], extended_pan_id[1],
          extended_pan_id[0], esp_zb_get_pan_id(), esp_zb_get_current_channel(), esp_zb_get_short_address()
        );
        esp_zb_bdb_start_top_level_commissioning(ESP_ZB_BDB_MODE_NETWORK_STEERING);
      } else {
        log_i("Restart network formation (status: %s)", esp_err_to_name(err_status));
        esp_zb_scheduler_alarm((esp_zb_callback_t)bdb_start_top_level_commissioning_cb, ESP_ZB_BDB_MODE_NETWORK_FORMATION, 1000);
      }
      break;
    // 表示设备加入网络成功
    case ESP_ZB_BDB_SIGNAL_STEERING:
      if (err_status == ESP_OK) {
        log_i("Network steering started");
      }
      break;
    // 有新设备加入或重新加入了网络,函数会从信号参数中提取出设备的短地址,并输出一条信息日志,打印出设备的短地址。
    // 调用esp_zb_zdo_find_on_off_light函数,在网络中查找On/Off灯具设备,并指定user_find_cb作为查找结果的回调函数
    case ESP_ZB_ZDO_SIGNAL_DEVICE_ANNCE:
      dev_annce_params = (esp_zb_zdo_signal_device_annce_params_t *)esp_zb_app_signal_get_params(p_sg_p);
      log_i("New device commissioned or rejoined (short: 0x%04hx)", dev_annce_params->device_short_addr);
      esp_zb_zdo_match_desc_req_param_t cmd_req;
      cmd_req.dst_nwk_addr = dev_annce_params->device_short_addr;
      cmd_req.addr_of_interest = dev_annce_params->device_short_addr;
      esp_zb_zdo_find_on_off_light(&cmd_req, user_find_cb, NULL);  // 在网络中查找On/Off灯具设备
      break;
    default:
      log_i("ZDO signal: %s (0x%x), status: %s", esp_zb_zdo_signal_to_string(sig_type), sig_type, esp_err_to_name(err_status));
      break;
  }
}

这一部分的内容相比于ED设备,在入网成功之后还会持续监听Zigbee网络中的On/Off灯具设备,并在检测到之后指定user_find_cb作为回调函数。

c
static void bind_cb(esp_zb_zdp_status_t zdo_status, void *user_ctx) {
  if (zdo_status == ESP_ZB_ZDP_STATUS_SUCCESS) {
    log_i("Bound successfully!");
    if (user_ctx) {
      light_bulb_device_params_t *light = (light_bulb_device_params_t *)user_ctx;
      log_i("The light originating from address(0x%x) on endpoint(%d)", light->short_addr, light->endpoint);
      free(light);
    }
  }
}

// 这个函数的主要作用是在发现了一个灯具设备后,尝试与该设备建立绑定关系,以便在未来可以控制该设备
// 这个代码会自动发现并绑定所有加入Zigbee网络的On/Off设备,无论有多少个设备。这种自动发现和绑定的机制使得网关可以动态地适应网络拓扑的变化,无需手动配置每个设备。
static void user_find_cb(esp_zb_zdp_status_t zdo_status, uint16_t addr, uint8_t endpoint, void *user_ctx) {
  if (zdo_status == ESP_ZB_ZDP_STATUS_SUCCESS) {
    log_i("Found light");
    esp_zb_zdo_bind_req_param_t bind_req;  // 用于存储即将发送的绑定请求
    light_bulb_device_params_t *light = (light_bulb_device_params_t *)malloc(sizeof(light_bulb_device_params_t));  // 用于存储被发现设备的信息
    light->endpoint = endpoint;  // 将light的endpoint字段设置为被发现设备的端点号
    light->short_addr = addr;    // 将light的short_addr字段设置为被发现设备的短地址
    esp_zb_ieee_address_by_short(light->short_addr, light->ieee_addr);  // 将被发现设备的短地址转换为IEEE地址,并存储到light的ieee_addr字段中
    esp_zb_get_long_address(bind_req.src_address);  // 获取当前设备的IEEE地址,并存储到bind_req的src_address字段中
    bind_req.src_endp = HA_ONOFF_SWITCH_ENDPOINT;  // 表示绑定请求的源端点为开关设备的端点号
    bind_req.cluster_id = ESP_ZB_ZCL_CLUSTER_ID_ON_OFF;  // 表示绑定请求的簇ID为On/Off簇
    bind_req.dst_addr_mode = ESP_ZB_ZDO_BIND_DST_ADDR_MODE_64_BIT_EXTENDED;  // 表示绑定请求的目标地址模式为64位扩展地址
    memcpy(bind_req.dst_address_u.addr_long, light->ieee_addr, sizeof(esp_zb_ieee_addr_t));  // 表示绑定请求的目标地址为被发现设备的IEEE地址
    bind_req.dst_endp = endpoint;  // 将bind_req的dst_endp字段设置为被发现设备的端点号
    bind_req.req_dst_addr = esp_zb_get_short_address();  // 表示绑定请求的目标地址为当前设备的短地址
    log_i("Try to bind On/Off");
    esp_zb_zdo_device_bind_req(&bind_req, bind_cb, (void *)light);
  }
}
  1. 设备初始化和网络组建:
  • 当Zigbee设备启动时,它会先初始化Zigbee协议栈,然后开始组建网络。

  • 如果设备是第一次启动或重启,它会尝试组建一个新的网络,并成为该网络的协调器。

  • 组建网络的过程通过调用esp_zb_bdb_start_top_level_commissioning函数来启动,将模式设置为ESP_ZB_BDB_MODE_NETWORK_FORMATION。

  • 如果网络组建成功,设备会输出网络的相关信息,如扩展PAN ID、PAN ID、信道和短地址。

  1. 设备加入网络:
  • 组建网络后,设备会尝试加入该网络,或者加入一个已经存在的网络。

  • 加入网络的过程通过调用esp_zb_bdb_start_top_level_commissioning函数来启动,并将模式设置为ESP_ZB_BDB_MODE_NETWORK_STEERING。

  • 如果设备成功加入网络,它会输出一条信息日志,表示网络加入已经开始。

  1. 设备发现和绑定:
  • 当有新设备加入网络时,网关会收到一个ESP_ZB_ZDO_SIGNAL_DEVICE_ANNCE信号,其中包含了新设备的短地址。

  • 网关会调用esp_zb_zdo_find_on_off_light函数,在网络中查找On/Off灯具设备,并指定user_find_cb作为查找结果的回调函数。

  • 在user_find_cb函数中,如果找到了匹配的设备,网关会尝试与该设备建立绑定关系,以便在未来可以控制该设备。

  • 绑定的过程通过调用esp_zb_zdo_device_bind_req函数来启动,并将绑定请求的参数封装在一个esp_zb_zdo_bind_req_param_t类型的结构体中。

  • 绑定请求中包含了源端点、目标端点、簇ID等信息,以及设备的IEEE地址和短地址。

  • 如果绑定成功,网关就可以通过向目标设备发送On/Off命令来控制该设备了。

  1. 设备控制:
  • 当需要控制设备时,网关会调用相应的命令函数,如esp_zb_zcl_on_off_cmd_req,并将命令参数封装在一个结构体中。

  • 命令函数会将命令发送给目标设备,并等待设备的响应。

  • 如果设备成功执行了命令,它会返回一个成功的响应,否则会返回一个错误码。

Powered by VitePress