-
Notifications
You must be signed in to change notification settings - Fork 175
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Loading status checks…
Merge branch 'thread_br/custom_cluster' into 'main'
thread_br: add thread br custom cluster See merge request app-frameworks/esp-matter!590
Showing
9 changed files
with
477 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
# Thread Border Router Cluster | ||
|
||
The Thread Border Router (BR) Cluster offers an interface for managing the ESP Thread BR. It allows users to perform various tasks such as configuring the dataset of the Thread network that the BR will form or join, start or stop Thread network. | ||
|
||
## 1. Cluster Identifiers | ||
|
||
| Identifier | Name | | ||
|------------|--------------------------| | ||
| 0x131BFC02 | **Thread Border Router** | | ||
|
||
## 2. Data Types | ||
|
||
### 2.1 ThreadRoleEnum Type | ||
|
||
This data type is derived from enum8. | ||
|
||
| Value | Name | Summary | Conformance | | ||
|-------|----------|-------------------------------------------|-------------| | ||
| 0 | Disabled | The Thread is disabled | M | | ||
| 1 | Detached | The Node is detached to a Thread network | M | | ||
| 2 | Child | The Node acts as a Child Role | M | | ||
| 3 | Router | The Node acts as a Router Role | M | | ||
| 4 | Leader | The Node acts as a Leader Role | M | | ||
|
||
## 2. Attributes | ||
|
||
| ID | Name | Type | Constranint | Quality | Default | Access | Conformance | | ||
|--------|-------------------|----------------|-------------|---------|---------|--------|-------------| | ||
| 0x0000 | **DatasetTlvs** | octstr | max254 | N | | R V | M | | ||
| 0x0001 | **Role** | ThreadRoleEnum | | | | R V | M | | ||
| 0x0002 | **BorderAgentId** | octstr | 16 | N | | R V | M | | ||
|
||
### 2.1 DatasetTlvs Attribute | ||
|
||
This attribute stores the dataset Tlvs of the Thread network that the BR will form or join. It will be updated after the ConfigureDatasetTlvs command is handled and the dataset is successfully committed. | ||
|
||
### 2.2 Role Attribute | ||
|
||
This attribute stores the Thread network role of the Thread BR. | ||
|
||
### 2.3 BorderAgentId Attribute | ||
|
||
This attribute stores the the randomly generated Border Agent ID. The typical use case of the ID is to be published in the MeshCoP mDNS service as the `id` TXT value for the client to identify this Border Router/Agent device. | ||
|
||
## 3. Commands | ||
|
||
| ID | Name | Direction | Response | Access | Conformance | | ||
|--------|--------------------------|----------------|----------|--------|-------------| | ||
| 0x0000 | **ConfigureDatasetTlvs** | client->server | Y | A | M | | ||
| 0x0001 | **StartThread** | client->server | Y | A | M | | ||
| 0x0002 | **StopThread** | client->server | Y | A | M | | ||
|
||
|
||
### 3.1 ConfigureDatasetTlvs Command | ||
|
||
The ConfigureDatasetTlvs command allows the Thread BR to configure the dataset Tlvs of its Thread network. The DatasetTlvs Attribute will be updated after the dataset is commited. | ||
|
||
The ConfigureDatasetTlvs command SHALL have the following data fields: | ||
|
||
| ID | Name | Type | Constraint | Quality | Default | Comformance | | ||
|----|--------------------|--------|------------|---------|---------|-------------| | ||
| 0 | **DatasetTlvsStr** | string | max508 | | | M | | ||
|
||
#### 3.1.1 DatasetTlvsStr Field | ||
|
||
This field is the dataset tlvs string which will be conmmited. | ||
|
||
### 3.2 StartThread Command | ||
|
||
The StartThread command allows devices to form or join Thread network. | ||
|
||
The StartThread command has no data field. | ||
|
||
### 3.3 StopThread Command | ||
|
||
The StopThread command allows devices to stop its Thread network. | ||
|
||
The StopThread command has no data field. |
250 changes: 250 additions & 0 deletions
250
components/esp_matter_thread_br/esp_matter_thread_br_cluster.cpp
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,250 @@ | ||
// Copyright 2023 Espressif Systems (Shanghai) PTE LTD | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
#include <esp_check.h> | ||
#include <esp_log.h> | ||
#include <esp_matter_thread_br_cluster.h> | ||
#include <esp_matter_thread_br_launcher.h> | ||
|
||
#include <app/util/attribute-storage.h> | ||
|
||
#define TAG "thread_br_custom_cluster" | ||
|
||
namespace esp_matter { | ||
|
||
static int hex_digit_to_int(char hex) | ||
{ | ||
if ('A' <= hex && hex <= 'F') { | ||
return 10 + hex - 'A'; | ||
} else if ('a' <= hex && hex <= 'f') { | ||
return 10 + hex - 'a'; | ||
} else if ('0' <= hex && hex <= '9') { | ||
return hex - '0'; | ||
} | ||
return -1; | ||
} | ||
|
||
static size_t hex_str_to_binary(const char *str, size_t str_len, uint8_t *buf, uint8_t buf_size) | ||
{ | ||
if (str_len % 2 != 0 || str_len / 2 > buf_size) { | ||
return 0; | ||
} | ||
for (size_t index = 0; index < str_len / 2; ++index) { | ||
int byte_h = hex_digit_to_int(str[2 * index]); | ||
int byte_l = hex_digit_to_int(str[2 * index + 1]); | ||
if (byte_h < 0 || byte_l < 0) { | ||
return 0; | ||
} | ||
buf[index] = (byte_h << 4) + byte_l; | ||
} | ||
return str_len / 2; | ||
} | ||
|
||
namespace cluster { | ||
namespace thread_br { | ||
|
||
namespace attribute { | ||
|
||
attribute_t *create_dataset_tlvs(cluster_t *cluster, uint8_t *value, uint16_t length) | ||
{ | ||
return esp_matter::attribute::create(cluster, dataset_tlvs::Id, ATTRIBUTE_FLAG_NONVOLATILE, | ||
esp_matter_octet_str(value, length)); | ||
} | ||
|
||
attribute_t *create_role(cluster_t *cluster, uint8_t value) | ||
{ | ||
return esp_matter::attribute::create(cluster, role::Id, ATTRIBUTE_FLAG_NONE, esp_matter_enum8(value)); | ||
} | ||
|
||
attribute_t *create_border_agent_id(cluster_t *cluster, uint8_t *value, uint16_t length) | ||
{ | ||
return esp_matter::attribute::create(cluster, border_agent_id::Id, ATTRIBUTE_FLAG_NONVOLATILE, | ||
esp_matter_octet_str(value, length)); | ||
} | ||
|
||
} // namespace attribute | ||
|
||
namespace command { | ||
|
||
static esp_err_t configure_dataset_tlvs_command_callback(const ConcreteCommandPath &command_path, TLVReader &tlv_data, | ||
void *opaque_ptr) | ||
{ | ||
uint16_t endpoint_id = command_path.mEndpointId; | ||
uint32_t cluster_id = command_path.mClusterId; | ||
uint32_t command_id = command_path.mCommandId; | ||
|
||
if (cluster_id != cluster::thread_br::Id || command_id != cluster::thread_br::command::configure_dataset_tlvs::Id) { | ||
ESP_LOGE(TAG, "Got thread_br command callback for some other command. This should not happen."); | ||
return ESP_FAIL; | ||
} | ||
|
||
chip::CharSpan dataset_tlvs_str; | ||
chip::TLV::TLVType outer; | ||
ESP_RETURN_ON_FALSE(tlv_data.GetType() == chip::TLV::kTLVType_Structure, ESP_FAIL, TAG, | ||
"TLV data is not a structure"); | ||
ESP_RETURN_ON_FALSE(tlv_data.EnterContainer(outer) == CHIP_NO_ERROR, ESP_FAIL, TAG, "Failed to enter container"); | ||
while (tlv_data.Next() == CHIP_NO_ERROR) { | ||
if (!chip::TLV::IsContextTag(tlv_data.GetTag())) { | ||
continue; | ||
} | ||
if (chip::TLV::TagNumFromTag(tlv_data.GetTag()) == 0 && tlv_data.GetType() == chip::TLV::kTLVType_UTF8String) { | ||
chip::app::DataModel::Decode(tlv_data, dataset_tlvs_str); | ||
} | ||
} | ||
ESP_RETURN_ON_FALSE(tlv_data.ExitContainer(outer) == CHIP_NO_ERROR, ESP_FAIL, TAG, "Failed to exit container"); | ||
const char *data = dataset_tlvs_str.data(); | ||
size_t size = dataset_tlvs_str.size(); | ||
otOperationalDatasetTlvs dataset_tlvs; | ||
dataset_tlvs.mLength = hex_str_to_binary(data, size, dataset_tlvs.mTlvs, sizeof(dataset_tlvs.mTlvs)); | ||
ESP_RETURN_ON_FALSE(dataset_tlvs.mLength > 0, ESP_ERR_INVALID_ARG, TAG, "Failed to parse dataset tlvs"); | ||
ESP_RETURN_ON_ERROR(set_thread_dataset_tlvs(&dataset_tlvs), TAG, "Failed to set Thread DatasetTlvs"); | ||
|
||
return ESP_OK; | ||
} | ||
|
||
static esp_err_t start_thread_command_callback(const ConcreteCommandPath &command_path, TLVReader &tlv_data, | ||
void *opaque_ptr) | ||
{ | ||
uint32_t cluster_id = command_path.mClusterId; | ||
uint32_t command_id = command_path.mCommandId; | ||
|
||
if (cluster_id != cluster::thread_br::Id || command_id != cluster::thread_br::command::start_thread::Id) { | ||
ESP_LOGE(TAG, "Got thread_br command callback for some other command. This should not happen."); | ||
return ESP_FAIL; | ||
} | ||
|
||
return set_thread_enabled(true); | ||
} | ||
|
||
static esp_err_t stop_thread_command_callback(const ConcreteCommandPath &command_path, TLVReader &tlv_data, | ||
void *opaque_ptr) | ||
{ | ||
uint32_t cluster_id = command_path.mClusterId; | ||
uint32_t command_id = command_path.mCommandId; | ||
|
||
if (cluster_id != cluster::thread_br::Id || command_id != cluster::thread_br::command::stop_thread::Id) { | ||
ESP_LOGE(TAG, "Got thread_br command callback for some other command. This should not happen."); | ||
return ESP_FAIL; | ||
} | ||
|
||
return set_thread_enabled(false); | ||
} | ||
|
||
command_t *create_configure_dataset_tlvs(cluster_t *cluster) | ||
{ | ||
return esp_matter::command::create(cluster, configure_dataset_tlvs::Id, COMMAND_FLAG_ACCEPTED | COMMAND_FLAG_CUSTOM, | ||
configure_dataset_tlvs_command_callback); | ||
} | ||
|
||
command_t *create_start_thread(cluster_t *cluster) | ||
{ | ||
return esp_matter::command::create(cluster, start_thread::Id, COMMAND_FLAG_ACCEPTED | COMMAND_FLAG_CUSTOM, | ||
start_thread_command_callback); | ||
} | ||
|
||
command_t *create_stop_thread(cluster_t *cluster) | ||
{ | ||
return esp_matter::command::create(cluster, stop_thread::Id, COMMAND_FLAG_ACCEPTED | COMMAND_FLAG_CUSTOM, | ||
stop_thread_command_callback); | ||
} | ||
|
||
} // namespace command | ||
|
||
using chip::app::AttributeAccessInterface; | ||
using chip::app::AttributeValueDecoder; | ||
using chip::app::AttributeValueEncoder; | ||
using chip::app::ConcreteDataAttributePath; | ||
using chip::app::ConcreteReadAttributePath; | ||
|
||
class ThreadBRAttrAccess : public AttributeAccessInterface { | ||
public: | ||
ThreadBRAttrAccess() | ||
: AttributeAccessInterface(chip::Optional<chip::EndpointId>::Missing(), cluster::thread_br::Id) | ||
{ | ||
} | ||
|
||
CHIP_ERROR Read(const ConcreteReadAttributePath &aPath, AttributeValueEncoder &aEncoder) override | ||
{ | ||
if (aPath.mClusterId != cluster::thread_br::Id) { | ||
return CHIP_ERROR_INVALID_ARGUMENT; | ||
} | ||
esp_err_t err = ESP_OK; | ||
if (aPath.mAttributeId == cluster::thread_br::attribute::dataset_tlvs::Id) { | ||
otOperationalDatasetTlvs dataset_tlvs; | ||
err = get_thread_dataset_tlvs(&dataset_tlvs); | ||
if (err != ESP_OK) { | ||
return CHIP_ERROR_INTERNAL; | ||
} | ||
return aEncoder.Encode(chip::ByteSpan(dataset_tlvs.mTlvs, dataset_tlvs.mLength)); | ||
} else if (aPath.mAttributeId == cluster::thread_br::attribute::role::Id) { | ||
uint8_t role = get_thread_role(); | ||
return aEncoder.Encode(role); | ||
} else if (aPath.mAttributeId == cluster::thread_br::attribute::border_agent_id::Id) { | ||
otBorderAgentId border_agent_id; | ||
err = get_border_agent_id(&border_agent_id); | ||
if (err != ESP_OK) { | ||
return CHIP_ERROR_INTERNAL; | ||
} | ||
return aEncoder.Encode(chip::ByteSpan(border_agent_id.mId, sizeof(border_agent_id.mId))); | ||
} | ||
return CHIP_NO_ERROR; | ||
} | ||
|
||
CHIP_ERROR Write(const ConcreteDataAttributePath &aPath, AttributeValueDecoder &aDecoder) override | ||
{ | ||
return CHIP_NO_ERROR; | ||
} | ||
}; | ||
|
||
ThreadBRAttrAccess g_attr_access; | ||
|
||
void thread_br_cluster_plugin_server_init_callback() | ||
{ | ||
registerAttributeAccessOverride(&g_attr_access); | ||
} | ||
|
||
const function_generic_t function_list[] = {}; | ||
const int function_flags = CLUSTER_FLAG_NONE; | ||
|
||
cluster_t *create(endpoint_t *endpoint, uint8_t flags) | ||
{ | ||
cluster_t *cluster = esp_matter::cluster::create(endpoint, Id, CLUSTER_FLAG_SERVER); | ||
if (!cluster) { | ||
ESP_LOGE(TAG, "Could not create cluster"); | ||
return NULL; | ||
} | ||
|
||
set_plugin_server_init_callback(cluster, thread_br_cluster_plugin_server_init_callback); | ||
add_function_list(cluster, function_list, function_flags); | ||
|
||
global::attribute::create_cluster_revision(cluster, 1); | ||
global::attribute::create_feature_map(cluster, 0); | ||
global::attribute::create_event_list(cluster, NULL, 0, 0); | ||
|
||
// Attribute managed internally | ||
attribute::create_dataset_tlvs(cluster, NULL, 0); | ||
attribute::create_role(cluster, 0); | ||
attribute::create_border_agent_id(cluster, NULL, 0); | ||
|
||
command::create_configure_dataset_tlvs(cluster); | ||
command::create_start_thread(cluster); | ||
command::create_stop_thread(cluster); | ||
|
||
return cluster; | ||
} | ||
|
||
} // namespace thread_br | ||
} // namespace cluster | ||
|
||
} // namespace esp_matter |
72 changes: 72 additions & 0 deletions
72
components/esp_matter_thread_br/esp_matter_thread_br_cluster.h
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
// Copyright 2023 Espressif Systems (Shanghai) PTE LTD | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
#pragma once | ||
|
||
#include <esp_err.h> | ||
#include <esp_matter.h> | ||
#include <esp_matter_core.h> | ||
|
||
namespace esp_matter { | ||
|
||
namespace cluster { | ||
namespace thread_br { | ||
|
||
static constexpr chip::ClusterId Id = 0x131BFC02; | ||
|
||
namespace attribute { | ||
|
||
namespace dataset_tlvs { | ||
static constexpr chip::AttributeId Id = 0x00000000; | ||
} // namespace dataset_tlvs | ||
attribute_t *create_dataset_tlvs(cluster_t *cluster, uint8_t *value, uint16_t length); | ||
|
||
namespace role { | ||
static constexpr chip::AttributeId Id = 0x00000001; | ||
} // namespace role | ||
attribute_t *create_role(cluster_t *cluster, uint8_t role); | ||
|
||
namespace border_agent_id { | ||
static constexpr chip::AttributeId Id = 0x00000002; | ||
} // namespace border_agent_id | ||
attribute_t *create_border_agent_id(cluster_t *cluster, uint8_t *value, uint16_t length); | ||
|
||
|
||
} // namespace attribute | ||
|
||
namespace command { | ||
|
||
namespace configure_dataset_tlvs { | ||
static constexpr chip::CommandId Id = 0x00000000; | ||
} // namespace configure_dataset_tlvs | ||
command_t *create_configure_dataset_tlvs(cluster_t *cluster); | ||
|
||
namespace start_thread { | ||
static constexpr chip::CommandId Id = 0x00000001; | ||
} // namespace start_thread | ||
command_t *create_start_thread(cluster_t *cluster); | ||
|
||
namespace stop_thread { | ||
static constexpr chip::CommandId Id = 0x00000002; | ||
} // namespace stop_thread | ||
command_t *create_stop_thread(cluster_t *cluster); | ||
|
||
} // namespace command | ||
|
||
cluster_t *create(endpoint_t *endpoint, uint8_t flags); | ||
|
||
} // namespace thread_br | ||
} // namespace cluster | ||
|
||
} // namespace esp_matter |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
<?xml version="1.0"?> | ||
<!-- | ||
Copyright (c) 2023 Project CHIP Authors | ||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
http://www.apache.org/licenses/LICENSE-2.0 | ||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. | ||
--> | ||
<configurator> | ||
<domain name="CHIP"/> | ||
<enum name="ThreadRoleEnum" type="enum8"> | ||
<cluster code="0x131BFC02"/> | ||
<item name="Disabled" value="0x00"/> | ||
<item name="Detached" value="0x01"/> | ||
<item name="Child" value="0x02"/> | ||
<item name="Router" value="0x03"/> | ||
<item name="Leader" value="0x04"/> | ||
</enum> | ||
<cluster> | ||
|
||
<name>ThreadBR</name> | ||
<domain>CHIP</domain> | ||
<code>0x131BFC02</code> | ||
<define>THREAD_BR_CLUSTER</define> | ||
|
||
<description>Attributes and commands for ThreadBR cluster.</description> | ||
|
||
<globalAttribute side="either" code="0xFFFD" value="2"/> | ||
|
||
<attribute side="server" code="0x0000" define="DATASET_TLVS" type="octet_string" length="254">DatasetTlvs</attribute> | ||
<attribute side="server" code="0x0001" define="ROLE" type="ThreadRoleEnum" min="0" max="4">Role</attribute> | ||
<attribute side="server" code="0x0002" define="BORDER_AGENT_ID" type="octet_string" length="16">BorderAgentId</attribute> | ||
|
||
<command source="client" code="0x00" name="ConfigureDatasetTlvs" optional="false"> | ||
<description>ConfigureDatasetTlvs command.</description> | ||
<arg name="DatasetTlvsStr" type="char_string"/> | ||
</command> | ||
|
||
<command source="client" code="0x01" name="StartThread" optional="false"> | ||
<description>StartThread command.</description> | ||
</command> | ||
|
||
<command source="client" code="0x02" name="StopThread" optional="false"> | ||
<description>StopThread command.</description> | ||
</command> | ||
|
||
</cluster> | ||
</configurator> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters