Skip to content

Commit f7397d8

Browse files
committed
Merge branch 'thread_br/custom_cluster' into 'main'
thread_br: add thread br custom cluster See merge request app-frameworks/esp-matter!590
2 parents c0fb895 + ba95323 commit f7397d8

9 files changed

+477
-1
lines changed

components/esp_matter_thread_br/CMakeLists.txt

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
set(SRCS_LIST )
22

33
if (CONFIG_OPENTHREAD_BORDER_ROUTER)
4-
list(APPEND SRCS_LIST "esp_matter_thread_br_launcher.cpp")
4+
list(APPEND SRCS_LIST "esp_matter_thread_br_cluster.cpp"
5+
"esp_matter_thread_br_launcher.cpp")
56
if (CONFIG_ENABLE_CHIP_SHELL AND CONFIG_OPENTHREAD_CLI)
67
list(APPEND SRCS_LIST "esp_matter_thread_br_console.cpp")
78
endif()
+78
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
# Thread Border Router Cluster
2+
3+
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.
4+
5+
## 1. Cluster Identifiers
6+
7+
| Identifier | Name |
8+
|------------|--------------------------|
9+
| 0x131BFC02 | **Thread Border Router** |
10+
11+
## 2. Data Types
12+
13+
### 2.1 ThreadRoleEnum Type
14+
15+
This data type is derived from enum8.
16+
17+
| Value | Name | Summary | Conformance |
18+
|-------|----------|-------------------------------------------|-------------|
19+
| 0 | Disabled | The Thread is disabled | M |
20+
| 1 | Detached | The Node is detached to a Thread network | M |
21+
| 2 | Child | The Node acts as a Child Role | M |
22+
| 3 | Router | The Node acts as a Router Role | M |
23+
| 4 | Leader | The Node acts as a Leader Role | M |
24+
25+
## 2. Attributes
26+
27+
| ID | Name | Type | Constranint | Quality | Default | Access | Conformance |
28+
|--------|-------------------|----------------|-------------|---------|---------|--------|-------------|
29+
| 0x0000 | **DatasetTlvs** | octstr | max254 | N | | R V | M |
30+
| 0x0001 | **Role** | ThreadRoleEnum | | | | R V | M |
31+
| 0x0002 | **BorderAgentId** | octstr | 16 | N | | R V | M |
32+
33+
### 2.1 DatasetTlvs Attribute
34+
35+
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.
36+
37+
### 2.2 Role Attribute
38+
39+
This attribute stores the Thread network role of the Thread BR.
40+
41+
### 2.3 BorderAgentId Attribute
42+
43+
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.
44+
45+
## 3. Commands
46+
47+
| ID | Name | Direction | Response | Access | Conformance |
48+
|--------|--------------------------|----------------|----------|--------|-------------|
49+
| 0x0000 | **ConfigureDatasetTlvs** | client->server | Y | A | M |
50+
| 0x0001 | **StartThread** | client->server | Y | A | M |
51+
| 0x0002 | **StopThread** | client->server | Y | A | M |
52+
53+
54+
### 3.1 ConfigureDatasetTlvs Command
55+
56+
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.
57+
58+
The ConfigureDatasetTlvs command SHALL have the following data fields:
59+
60+
| ID | Name | Type | Constraint | Quality | Default | Comformance |
61+
|----|--------------------|--------|------------|---------|---------|-------------|
62+
| 0 | **DatasetTlvsStr** | string | max508 | | | M |
63+
64+
#### 3.1.1 DatasetTlvsStr Field
65+
66+
This field is the dataset tlvs string which will be conmmited.
67+
68+
### 3.2 StartThread Command
69+
70+
The StartThread command allows devices to form or join Thread network.
71+
72+
The StartThread command has no data field.
73+
74+
### 3.3 StopThread Command
75+
76+
The StopThread command allows devices to stop its Thread network.
77+
78+
The StopThread command has no data field.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
// Copyright 2023 Espressif Systems (Shanghai) PTE LTD
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#include <esp_check.h>
16+
#include <esp_log.h>
17+
#include <esp_matter_thread_br_cluster.h>
18+
#include <esp_matter_thread_br_launcher.h>
19+
20+
#include <app/util/attribute-storage.h>
21+
22+
#define TAG "thread_br_custom_cluster"
23+
24+
namespace esp_matter {
25+
26+
static int hex_digit_to_int(char hex)
27+
{
28+
if ('A' <= hex && hex <= 'F') {
29+
return 10 + hex - 'A';
30+
} else if ('a' <= hex && hex <= 'f') {
31+
return 10 + hex - 'a';
32+
} else if ('0' <= hex && hex <= '9') {
33+
return hex - '0';
34+
}
35+
return -1;
36+
}
37+
38+
static size_t hex_str_to_binary(const char *str, size_t str_len, uint8_t *buf, uint8_t buf_size)
39+
{
40+
if (str_len % 2 != 0 || str_len / 2 > buf_size) {
41+
return 0;
42+
}
43+
for (size_t index = 0; index < str_len / 2; ++index) {
44+
int byte_h = hex_digit_to_int(str[2 * index]);
45+
int byte_l = hex_digit_to_int(str[2 * index + 1]);
46+
if (byte_h < 0 || byte_l < 0) {
47+
return 0;
48+
}
49+
buf[index] = (byte_h << 4) + byte_l;
50+
}
51+
return str_len / 2;
52+
}
53+
54+
namespace cluster {
55+
namespace thread_br {
56+
57+
namespace attribute {
58+
59+
attribute_t *create_dataset_tlvs(cluster_t *cluster, uint8_t *value, uint16_t length)
60+
{
61+
return esp_matter::attribute::create(cluster, dataset_tlvs::Id, ATTRIBUTE_FLAG_NONVOLATILE,
62+
esp_matter_octet_str(value, length));
63+
}
64+
65+
attribute_t *create_role(cluster_t *cluster, uint8_t value)
66+
{
67+
return esp_matter::attribute::create(cluster, role::Id, ATTRIBUTE_FLAG_NONE, esp_matter_enum8(value));
68+
}
69+
70+
attribute_t *create_border_agent_id(cluster_t *cluster, uint8_t *value, uint16_t length)
71+
{
72+
return esp_matter::attribute::create(cluster, border_agent_id::Id, ATTRIBUTE_FLAG_NONVOLATILE,
73+
esp_matter_octet_str(value, length));
74+
}
75+
76+
} // namespace attribute
77+
78+
namespace command {
79+
80+
static esp_err_t configure_dataset_tlvs_command_callback(const ConcreteCommandPath &command_path, TLVReader &tlv_data,
81+
void *opaque_ptr)
82+
{
83+
uint16_t endpoint_id = command_path.mEndpointId;
84+
uint32_t cluster_id = command_path.mClusterId;
85+
uint32_t command_id = command_path.mCommandId;
86+
87+
if (cluster_id != cluster::thread_br::Id || command_id != cluster::thread_br::command::configure_dataset_tlvs::Id) {
88+
ESP_LOGE(TAG, "Got thread_br command callback for some other command. This should not happen.");
89+
return ESP_FAIL;
90+
}
91+
92+
chip::CharSpan dataset_tlvs_str;
93+
chip::TLV::TLVType outer;
94+
ESP_RETURN_ON_FALSE(tlv_data.GetType() == chip::TLV::kTLVType_Structure, ESP_FAIL, TAG,
95+
"TLV data is not a structure");
96+
ESP_RETURN_ON_FALSE(tlv_data.EnterContainer(outer) == CHIP_NO_ERROR, ESP_FAIL, TAG, "Failed to enter container");
97+
while (tlv_data.Next() == CHIP_NO_ERROR) {
98+
if (!chip::TLV::IsContextTag(tlv_data.GetTag())) {
99+
continue;
100+
}
101+
if (chip::TLV::TagNumFromTag(tlv_data.GetTag()) == 0 && tlv_data.GetType() == chip::TLV::kTLVType_UTF8String) {
102+
chip::app::DataModel::Decode(tlv_data, dataset_tlvs_str);
103+
}
104+
}
105+
ESP_RETURN_ON_FALSE(tlv_data.ExitContainer(outer) == CHIP_NO_ERROR, ESP_FAIL, TAG, "Failed to exit container");
106+
const char *data = dataset_tlvs_str.data();
107+
size_t size = dataset_tlvs_str.size();
108+
otOperationalDatasetTlvs dataset_tlvs;
109+
dataset_tlvs.mLength = hex_str_to_binary(data, size, dataset_tlvs.mTlvs, sizeof(dataset_tlvs.mTlvs));
110+
ESP_RETURN_ON_FALSE(dataset_tlvs.mLength > 0, ESP_ERR_INVALID_ARG, TAG, "Failed to parse dataset tlvs");
111+
ESP_RETURN_ON_ERROR(set_thread_dataset_tlvs(&dataset_tlvs), TAG, "Failed to set Thread DatasetTlvs");
112+
113+
return ESP_OK;
114+
}
115+
116+
static esp_err_t start_thread_command_callback(const ConcreteCommandPath &command_path, TLVReader &tlv_data,
117+
void *opaque_ptr)
118+
{
119+
uint32_t cluster_id = command_path.mClusterId;
120+
uint32_t command_id = command_path.mCommandId;
121+
122+
if (cluster_id != cluster::thread_br::Id || command_id != cluster::thread_br::command::start_thread::Id) {
123+
ESP_LOGE(TAG, "Got thread_br command callback for some other command. This should not happen.");
124+
return ESP_FAIL;
125+
}
126+
127+
return set_thread_enabled(true);
128+
}
129+
130+
static esp_err_t stop_thread_command_callback(const ConcreteCommandPath &command_path, TLVReader &tlv_data,
131+
void *opaque_ptr)
132+
{
133+
uint32_t cluster_id = command_path.mClusterId;
134+
uint32_t command_id = command_path.mCommandId;
135+
136+
if (cluster_id != cluster::thread_br::Id || command_id != cluster::thread_br::command::stop_thread::Id) {
137+
ESP_LOGE(TAG, "Got thread_br command callback for some other command. This should not happen.");
138+
return ESP_FAIL;
139+
}
140+
141+
return set_thread_enabled(false);
142+
}
143+
144+
command_t *create_configure_dataset_tlvs(cluster_t *cluster)
145+
{
146+
return esp_matter::command::create(cluster, configure_dataset_tlvs::Id, COMMAND_FLAG_ACCEPTED | COMMAND_FLAG_CUSTOM,
147+
configure_dataset_tlvs_command_callback);
148+
}
149+
150+
command_t *create_start_thread(cluster_t *cluster)
151+
{
152+
return esp_matter::command::create(cluster, start_thread::Id, COMMAND_FLAG_ACCEPTED | COMMAND_FLAG_CUSTOM,
153+
start_thread_command_callback);
154+
}
155+
156+
command_t *create_stop_thread(cluster_t *cluster)
157+
{
158+
return esp_matter::command::create(cluster, stop_thread::Id, COMMAND_FLAG_ACCEPTED | COMMAND_FLAG_CUSTOM,
159+
stop_thread_command_callback);
160+
}
161+
162+
} // namespace command
163+
164+
using chip::app::AttributeAccessInterface;
165+
using chip::app::AttributeValueDecoder;
166+
using chip::app::AttributeValueEncoder;
167+
using chip::app::ConcreteDataAttributePath;
168+
using chip::app::ConcreteReadAttributePath;
169+
170+
class ThreadBRAttrAccess : public AttributeAccessInterface {
171+
public:
172+
ThreadBRAttrAccess()
173+
: AttributeAccessInterface(chip::Optional<chip::EndpointId>::Missing(), cluster::thread_br::Id)
174+
{
175+
}
176+
177+
CHIP_ERROR Read(const ConcreteReadAttributePath &aPath, AttributeValueEncoder &aEncoder) override
178+
{
179+
if (aPath.mClusterId != cluster::thread_br::Id) {
180+
return CHIP_ERROR_INVALID_ARGUMENT;
181+
}
182+
esp_err_t err = ESP_OK;
183+
if (aPath.mAttributeId == cluster::thread_br::attribute::dataset_tlvs::Id) {
184+
otOperationalDatasetTlvs dataset_tlvs;
185+
err = get_thread_dataset_tlvs(&dataset_tlvs);
186+
if (err != ESP_OK) {
187+
return CHIP_ERROR_INTERNAL;
188+
}
189+
return aEncoder.Encode(chip::ByteSpan(dataset_tlvs.mTlvs, dataset_tlvs.mLength));
190+
} else if (aPath.mAttributeId == cluster::thread_br::attribute::role::Id) {
191+
uint8_t role = get_thread_role();
192+
return aEncoder.Encode(role);
193+
} else if (aPath.mAttributeId == cluster::thread_br::attribute::border_agent_id::Id) {
194+
otBorderAgentId border_agent_id;
195+
err = get_border_agent_id(&border_agent_id);
196+
if (err != ESP_OK) {
197+
return CHIP_ERROR_INTERNAL;
198+
}
199+
return aEncoder.Encode(chip::ByteSpan(border_agent_id.mId, sizeof(border_agent_id.mId)));
200+
}
201+
return CHIP_NO_ERROR;
202+
}
203+
204+
CHIP_ERROR Write(const ConcreteDataAttributePath &aPath, AttributeValueDecoder &aDecoder) override
205+
{
206+
return CHIP_NO_ERROR;
207+
}
208+
};
209+
210+
ThreadBRAttrAccess g_attr_access;
211+
212+
void thread_br_cluster_plugin_server_init_callback()
213+
{
214+
registerAttributeAccessOverride(&g_attr_access);
215+
}
216+
217+
const function_generic_t function_list[] = {};
218+
const int function_flags = CLUSTER_FLAG_NONE;
219+
220+
cluster_t *create(endpoint_t *endpoint, uint8_t flags)
221+
{
222+
cluster_t *cluster = esp_matter::cluster::create(endpoint, Id, CLUSTER_FLAG_SERVER);
223+
if (!cluster) {
224+
ESP_LOGE(TAG, "Could not create cluster");
225+
return NULL;
226+
}
227+
228+
set_plugin_server_init_callback(cluster, thread_br_cluster_plugin_server_init_callback);
229+
add_function_list(cluster, function_list, function_flags);
230+
231+
global::attribute::create_cluster_revision(cluster, 1);
232+
global::attribute::create_feature_map(cluster, 0);
233+
global::attribute::create_event_list(cluster, NULL, 0, 0);
234+
235+
// Attribute managed internally
236+
attribute::create_dataset_tlvs(cluster, NULL, 0);
237+
attribute::create_role(cluster, 0);
238+
attribute::create_border_agent_id(cluster, NULL, 0);
239+
240+
command::create_configure_dataset_tlvs(cluster);
241+
command::create_start_thread(cluster);
242+
command::create_stop_thread(cluster);
243+
244+
return cluster;
245+
}
246+
247+
} // namespace thread_br
248+
} // namespace cluster
249+
250+
} // namespace esp_matter

0 commit comments

Comments
 (0)