From 2a2122e9e5ffac4a645feb7d068ba41f79a44939 Mon Sep 17 00:00:00 2001 From: WanqQixiang Date: Wed, 26 Jul 2023 19:53:39 +0800 Subject: [PATCH] controller: add matter controller cluster --- .../esp_matter_controller/CMakeLists.txt | 25 +- components/esp_matter_controller/Kconfig | 7 + .../controller_custom_cluster/README.md | 110 ++ .../matter_controller_cluster.cpp | 1039 +++++++++++++++++ .../matter_controller_cluster.h | 117 ++ .../matter_controller_device_mgr.cpp | 520 +++++++++ .../matter_controller_device_mgr.h | 57 + .../esp_matter_controller_cluster_command.cpp | 7 +- .../esp_matter_controller_read_command.cpp | 2 +- ...sp_matter_controller_subscribe_command.cpp | 2 +- .../esp_matter_controller_utils.cpp | 19 + .../esp_matter_controller_utils.h | 16 + .../esp_matter_controller_write_command.cpp | 5 +- .../esp_matter_controller/idf_component.yml | 1 + examples/controller/README.md | 6 +- examples/controller/main/app_main.cpp | 12 +- 16 files changed, 1926 insertions(+), 19 deletions(-) create mode 100644 components/esp_matter_controller/controller_custom_cluster/README.md create mode 100644 components/esp_matter_controller/controller_custom_cluster/matter_controller_cluster.cpp create mode 100644 components/esp_matter_controller/controller_custom_cluster/matter_controller_cluster.h create mode 100644 components/esp_matter_controller/controller_custom_cluster/matter_controller_device_mgr.cpp create mode 100644 components/esp_matter_controller/controller_custom_cluster/matter_controller_device_mgr.h diff --git a/components/esp_matter_controller/CMakeLists.txt b/components/esp_matter_controller/CMakeLists.txt index 255ff95ed..062713d2a 100644 --- a/components/esp_matter_controller/CMakeLists.txt +++ b/components/esp_matter_controller/CMakeLists.txt @@ -1,19 +1,24 @@ set(src_dirs_list ) set(include_dirs_list ) set(exclude_srcs_list ) -if (CONFIG_ESP_MATTER_CONTROLLER_ENABLE) - list(APPEND src_dirs_list . logger/zap-generated) - list(APPEND include_dirs_list . logger) -endif() -if (NOT CONFIG_ESP_MATTER_COMMISSIONER_ENABLE) - list(APPEND exclude_srcs_list esp_matter_commissioner.cpp - esp_matter_controller_pairing_command.cpp - esp_matter_attestation_trust_store.cpp - esp_matter_controller_group_settings.cpp) +if (CONFIG_ESP_MATTER_CONTROLLER_ENABLE) + list(APPEND src_dirs_list "${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}/logger/zap-generated") + list(APPEND include_dirs_list "${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}/logger") + + if (CONFIG_ESP_MATTER_CONTROLLER_CUSTOM_CLUSTER_ENABLE) + list(APPEND src_dirs_list "${CMAKE_CURRENT_SOURCE_DIR}/controller_custom_cluster") + list(APPEND include_dirs_list "${CMAKE_CURRENT_SOURCE_DIR}/controller_custom_cluster") + endif() + if (NOT CONFIG_ESP_MATTER_COMMISSIONER_ENABLE) + list(APPEND exclude_srcs_list "${CMAKE_CURRENT_SOURCE_DIR}/esp_matter_commissioner.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/esp_matter_controller_pairing_command.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/esp_matter_attestation_trust_store.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/esp_matter_controller_group_settings.cpp") + endif() endif() idf_component_register(SRC_DIRS ${src_dirs_list} EXCLUDE_SRCS ${exclude_srcs_list} INCLUDE_DIRS ${include_dirs_list} - REQUIRES chip esp_matter esp_matter_console json_parser spiffs) + REQUIRES chip esp_matter esp_matter_console json_parser spiffs esp_http_client json_generator) diff --git a/components/esp_matter_controller/Kconfig b/components/esp_matter_controller/Kconfig index 10ec8f30b..d2ed0720c 100644 --- a/components/esp_matter_controller/Kconfig +++ b/components/esp_matter_controller/Kconfig @@ -20,6 +20,13 @@ menu "ESP Matter Controller" help Maximum number of active device the commissioner supports. + config ESP_MATTER_CONTROLLER_CUSTOM_CLUSTER_ENABLE + bool "Enable controller custom cluster" + depends on !ESP_MATTER_COMMISSIONER_ENABLE + default y + help + Enable the custom cluster of matter controller in the ESP Matter controller for Rainmaker Fabric suppport. + choice ESP_MATTER_COMMISSIONER_ATTESTATION_TRUST_STORE prompt "Attestation Trust Store" depends on ESP_MATTER_COMMISSIONER_ENABLE diff --git a/components/esp_matter_controller/controller_custom_cluster/README.md b/components/esp_matter_controller/controller_custom_cluster/README.md new file mode 100644 index 000000000..3165cf5ed --- /dev/null +++ b/components/esp_matter_controller/controller_custom_cluster/README.md @@ -0,0 +1,110 @@ +# Matter Controller Cluster + +The Matter Controller Cluster offers an interface for managing the ESP Matter Controller. It allows users to perform various tasks, such as authorizing the controller to login to the cloud, updating the controller's NOC to the Rainmaker-Fabric User NOC, and notifying the controller to update the device list. + +## 1. Cluster Identifiers + +| Identifier | Name | +|------------|-----------------------| +| 0x131BFC01 | **Matter Controller** | + +## 2. Attributes + +| ID | Name | Type | Constranint | Quality | Default | Access | Conformance | +|--------|-------------------------|----------------|-------------|---------|---------|--------|-------------| +| 0x0000 | **RefreshToken** | string | | N | | R V | M | +| 0x0001 | **AccessToken** | string | | N | | R V | M | +| 0x0002 | **Authorized** | bool | | N | false | R V | M | +| 0x0003 | **UserNOCInstalled** | bool | | N | false | R V | M | +| 0x0004 | **EndpointURL** | string | | N | | R V | M | +| 0x0005 | **RainmakerGroupId** | string | | N | | R V | M | +| 0x0006 | **UserNOCFabricIndex** | uint8_t | | N | | R V | M | + +### 2.1 RefreshToken Attribute + +This attribute stores the refresh token. For the HTTP REST Authenticated APIs, the refresh_token SHALL be used to fetch the access_token and the access_token will be passed in the "Authorization" HTTP header as the authentication token. + +Note: The access_token validity will expire after 1 hour. + +### 2.2 AccessToken Attribute + +This attribute stores the access token. It will be updated after the controller is authorized successfully. For the HTTP REST Authenticated APIs, the access_token will be passed in "Authorization" HTTP header as the authentication token. If the HTTP REST Authenticated APIs fail with decription "Unauthorized", the access_token will be updated. + +### 2.3 Authorized Attribute + +This attribute indicates the authorization status of the controller after joining the Rainmaker Fabric. After the access_token is fetched, the Authorized value will be set to true. And it will be set to false after 1 hour. If the HTTP REST Authenticated APIs fail with decription "Unauthorized", this attribute will be also set to false. + +### 2.4 UserNOCInstalled Attribute + +This attribute indicates whether the User NOC is installed in the controller. After the controller is authorized, the controller will generate a CSR and send it with the RainmakerGroupId to the cloud. The response from the cloud will include the new User NOC which will be installed in the controller. + +### 2.5 EndpointURL Attribute + +This attribute stores the Endpoint URL that is used for HTTP REST APIs. + +### 2.6 RainmakerGroupId Attribute + +This attribute stores the Rainmaker Group Id which is bound to the Matter Fabric Id. + +### 2.7 UserNOCFabricIndex Attribute + +This attribute stores the fabric index of the fabric where the user NOC is installed. It will be updated after the user NOC is installed. + +## 3. Commands + +| ID | Name | Direction | Response | Access | Conformance | +|--------|--------------------------|----------------|----------|--------|-------------| +| 0x0000 | **AppendRefreshToken** | client->server | Y | A | M | +| 0x0001 | **ResetRefreshToken** | client->server | Y | A | M | +| 0x0002 | **Authorize** | client->server | Y | A | M | +| 0x0003 | **UpdateUserNOC** | client->server | Y | A | M | +| 0x0004 | **UpdateDeviceList** | client->server | Y | A | M | + + +### 3.1 AppendRefreshToken Command + +The AppendRefreshToken command allows the controller to write the RefreshToken Attribute in several commands. It will append the current RefreshToken attribute. The RefreshToken is about 1700 Bytes which is much longer than the UDP MTU(1280 Bytes) so it should be send it separately. + +The AppendRefreshToken command SHALL have the following data fields: + +| ID | Name | Type | Constraint | Quality | Default | Comformance | +|----|--------------------------|--------|------------|---------|---------|-------------| +| 0 | **AppendedRefreshToken** | string | max 1024 | | | M | + +#### 3.1.1 AppendedRefreshToken Field + +This field is the string which will be appended to the current RefreshToken Attribute. + +### 3.2 ResetRefreshToken Command + +The ResetRefreshToken command allows devices to reset it RefreshToken to an empty string. + +The ResetRefreshToken has no data field. + +### 3.3 Authorize Command + +The Authorize command allows the controller to login the cloud HTTP server and fetch the AccessToken with the HTTP REST Authenticated APIs. + +After AccessToken is fetched, the controller will get the RainmakerGroupId according to the FabricId of the command object. + +The Authorize command SHALL have the following data fields: + +| ID | Name | Type | Constraint | Quality | Default | Comformance | +|----|------------------|--------|------------|---------|---------|-------------| +| 0 | **EndpointURL** | string | max 64 | | | M | + +#### 3.3.1 EndpointURL Field + +This field is the EndpointURL. After the controller is authorized successfully, this value will be written to the EndpointURL Attribute. + +### 3.4 UpdateUserNOC Command + +The UpdateUserNOC command allows the controller to fetch the Rainmaker User NOC after the is authorized. When receiving this command, the controller will generate a new CSR and send it with the RainmakerGroupId to the cloud. After receiving the response, it will install the RainmakerUserNOC in the response. + +The UpdateUserNOC command has no data field. + +### 3.5 UpdateDeviceList Command + +The UpdateDeviceList command notifies the controller to update its maintaining Matter device list. After this command is received, the controller will query the device list in the Rainmaker Group(Matter Fabric) from the cloud. This command SHALL be called after the controller has updated its NOC and when a new Matter device is commissioned into the Matter Fabric. + +The UpdateDeviceList command has no data field. diff --git a/components/esp_matter_controller/controller_custom_cluster/matter_controller_cluster.cpp b/components/esp_matter_controller/controller_custom_cluster/matter_controller_cluster.cpp new file mode 100644 index 000000000..d09ca869c --- /dev/null +++ b/components/esp_matter_controller/controller_custom_cluster/matter_controller_cluster.cpp @@ -0,0 +1,1039 @@ +// 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#define TAG "controller_custom_cluster" + +using chip::ByteSpan; +using chip::MutableByteSpan; +using chip::to_underlying; +using chip::app::ConcreteCommandPath; +using chip::Platform::ScopedMemoryBufferWithSize; +using chip::TLV::TLVReader; +using namespace chip::DeviceLayer; +using chip::System::Clock::Seconds32; + +namespace esp_matter { +namespace cluster { +namespace matter_controller { + +namespace attribute { +static esp_err_t refresh_token_attribute_update(uint16_t endpoint_id, char *refresh_token, uint16_t length) +{ + ESP_RETURN_ON_FALSE(refresh_token, ESP_ERR_INVALID_ARG, TAG, "refresh_token cannot be NULL"); + esp_matter_attr_val_t val = esp_matter_long_char_str(refresh_token, length); + uint32_t cluster_id = matter_controller::Id; + uint32_t attribute_id = refresh_token::Id; + return esp_matter::attribute::update(endpoint_id, cluster_id, attribute_id, &val); +} + +esp_err_t refresh_token_attribute_get(uint16_t endpoint_id, char *refresh_token) +{ + ESP_RETURN_ON_FALSE(refresh_token, ESP_ERR_INVALID_ARG, TAG, "refresh_token cannot be NULL"); + node_t *node = node::get(); + endpoint_t *endpoint = endpoint::get(node, endpoint_id); + cluster_t *cluster = cluster::get(endpoint, matter_controller::Id); + attribute_t *attribute = esp_matter::attribute::get(cluster, refresh_token::Id); + ESP_RETURN_ON_FALSE(attribute, ESP_FAIL, TAG, "Could not find refresh_token attribue"); + esp_matter_attr_val_t raw_val = esp_matter_invalid(NULL); + esp_matter::attribute::get_val(attribute, &raw_val); + ESP_RETURN_ON_FALSE(raw_val.type == ESP_MATTER_VAL_TYPE_LONG_CHAR_STRING, ESP_FAIL, TAG, "Invalid Attribute type"); + strncpy(refresh_token, (const char *)raw_val.val.a.b, raw_val.val.a.s); + refresh_token[raw_val.val.a.s] = '\0'; + return ESP_OK; +} + +attribute_t *create_refresh_token(cluster_t *cluster, char *value, uint16_t length) +{ + return esp_matter::attribute::create(cluster, refresh_token::Id, ATTRIBUTE_FLAG_NONVOLATILE, + esp_matter_long_char_str(value, length)); +} + +static esp_err_t access_token_attribute_update(uint16_t endpoint_id, char *access_token, uint16_t length) +{ + ESP_RETURN_ON_FALSE(access_token, ESP_ERR_INVALID_ARG, TAG, "access_token cannot be NULL"); + esp_matter_attr_val_t val = esp_matter_long_char_str(access_token, length); + uint32_t cluster_id = matter_controller::Id; + uint32_t attribute_id = access_token::Id; + return esp_matter::attribute::update(endpoint_id, cluster_id, attribute_id, &val); +} + +esp_err_t access_token_attribute_get(uint16_t endpoint_id, char *access_token) +{ + ESP_RETURN_ON_FALSE(access_token, ESP_ERR_INVALID_ARG, TAG, "access_token cannot be NULL"); + node_t *node = node::get(); + endpoint_t *endpoint = endpoint::get(node, endpoint_id); + cluster_t *cluster = cluster::get(endpoint, matter_controller::Id); + attribute_t *attribute = esp_matter::attribute::get(cluster, access_token::Id); + ESP_RETURN_ON_FALSE(attribute, ESP_FAIL, TAG, "Could not find access_token attribue"); + esp_matter_attr_val_t raw_val = esp_matter_invalid(NULL); + esp_matter::attribute::get_val(attribute, &raw_val); + ESP_RETURN_ON_FALSE(raw_val.type == ESP_MATTER_VAL_TYPE_LONG_CHAR_STRING, ESP_FAIL, TAG, "Invalid Attribute type"); + strncpy(access_token, (const char *)raw_val.val.a.b, raw_val.val.a.s); + access_token[raw_val.val.a.s] = '\0'; + return ESP_OK; +} + +attribute_t *create_access_token(cluster_t *cluster, char *value, uint16_t length) +{ + return esp_matter::attribute::create(cluster, access_token::Id, ATTRIBUTE_FLAG_NONVOLATILE, + esp_matter_long_char_str(value, length)); +} + +esp_err_t authorized_attribute_update(uint16_t endpoint_id, bool authorized) +{ + esp_matter_attr_val_t val = esp_matter_bool(authorized); + uint32_t cluster_id = matter_controller::Id; + uint32_t attribute_id = authorized::Id; + return esp_matter::attribute::update(endpoint_id, cluster_id, attribute_id, &val); +} + +esp_err_t authorized_attribute_get(uint16_t endpoint_id, bool &authorized) +{ + node_t *node = node::get(); + endpoint_t *endpoint = endpoint::get(node, endpoint_id); + cluster_t *cluster = cluster::get(endpoint, matter_controller::Id); + attribute_t *attribute = esp_matter::attribute::get(cluster, authorized::Id); + ESP_RETURN_ON_FALSE(attribute, ESP_FAIL, TAG, "Could not find authorized attribue"); + esp_matter_attr_val_t raw_val = esp_matter_invalid(NULL); + esp_matter::attribute::get_val(attribute, &raw_val); + ESP_RETURN_ON_FALSE(raw_val.type == ESP_MATTER_VAL_TYPE_BOOLEAN, ESP_FAIL, TAG, "Invalid Attribute type"); + authorized = raw_val.val.b; + return ESP_OK; +} + +attribute_t *create_authorized(cluster_t *cluster, bool value) +{ + return esp_matter::attribute::create(cluster, authorized::Id, ATTRIBUTE_FLAG_NONVOLATILE, esp_matter_bool(value)); +} + +static esp_err_t user_noc_installed_attribute_update(uint16_t endpoint_id, bool user_noc_installed) +{ + esp_matter_attr_val_t val = esp_matter_bool(user_noc_installed); + uint32_t cluster_id = matter_controller::Id; + uint32_t attribute_id = user_noc_installed::Id; + return esp_matter::attribute::update(endpoint_id, cluster_id, attribute_id, &val); +} + +esp_err_t user_noc_installed_attribute_get(uint16_t endpoint_id, bool &user_noc_installed) +{ + node_t *node = node::get(); + endpoint_t *endpoint = endpoint::get(node, endpoint_id); + cluster_t *cluster = cluster::get(endpoint, matter_controller::Id); + attribute_t *attribute = esp_matter::attribute::get(cluster, user_noc_installed::Id); + ESP_RETURN_ON_FALSE(attribute, ESP_FAIL, TAG, "Could not find user_noc_installed attribue"); + esp_matter_attr_val_t raw_val = esp_matter_invalid(NULL); + esp_matter::attribute::get_val(attribute, &raw_val); + ESP_RETURN_ON_FALSE(raw_val.type == ESP_MATTER_VAL_TYPE_BOOLEAN, ESP_FAIL, TAG, "Invalid Attribute type"); + user_noc_installed = raw_val.val.b; + return ESP_OK; +} + +attribute_t *create_user_noc_installed(cluster_t *cluster, bool value) +{ + return esp_matter::attribute::create(cluster, user_noc_installed::Id, ATTRIBUTE_FLAG_NONVOLATILE, + esp_matter_bool(value)); +} + +static esp_err_t endpoint_url_attribute_update(uint16_t endpoint_id, char *endpoint_url, uint16_t length) +{ + ESP_RETURN_ON_FALSE(endpoint_url, ESP_ERR_INVALID_ARG, TAG, "endpoint_url cannot be NULL"); + esp_matter_attr_val_t val = esp_matter_char_str(endpoint_url, length); + uint32_t cluster_id = matter_controller::Id; + uint32_t attribute_id = endpoint_url::Id; + return esp_matter::attribute::update(endpoint_id, cluster_id, attribute_id, &val); +} + +esp_err_t endpoint_url_attribute_get(uint16_t endpoint_id, char *endpoint_url) +{ + ESP_RETURN_ON_FALSE(endpoint_url, ESP_ERR_INVALID_ARG, TAG, "endpoint_url cannot be NULL"); + node_t *node = node::get(); + endpoint_t *endpoint = endpoint::get(node, endpoint_id); + cluster_t *cluster = cluster::get(endpoint, matter_controller::Id); + attribute_t *attribute = esp_matter::attribute::get(cluster, endpoint_url::Id); + ESP_RETURN_ON_FALSE(attribute, ESP_FAIL, TAG, "Could not find endpoint_url attribue"); + esp_matter_attr_val_t raw_val = esp_matter_invalid(NULL); + esp_matter::attribute::get_val(attribute, &raw_val); + ESP_RETURN_ON_FALSE(raw_val.type == ESP_MATTER_VAL_TYPE_CHAR_STRING, ESP_FAIL, TAG, "Invalid Attribute type"); + strncpy(endpoint_url, (const char *)raw_val.val.a.b, raw_val.val.a.s); + endpoint_url[raw_val.val.a.s] = '\0'; + return ESP_OK; +} + +attribute_t *create_endpoint_url(cluster_t *cluster, char *value, uint16_t length) +{ + return esp_matter::attribute::create(cluster, endpoint_url::Id, ATTRIBUTE_FLAG_NONVOLATILE, + esp_matter_char_str(value, length)); +} + +static esp_err_t rainmaker_group_id_attribute_update(uint16_t endpoint_id, char *group_id, uint16_t length) +{ + ESP_RETURN_ON_FALSE(group_id, ESP_ERR_INVALID_ARG, TAG, "group_id cannot be NULL"); + esp_matter_attr_val_t val = esp_matter_char_str(group_id, length); + uint32_t cluster_id = matter_controller::Id; + uint32_t attribute_id = rainmaker_group_id::Id; + return esp_matter::attribute::update(endpoint_id, cluster_id, attribute_id, &val); +} + +esp_err_t rainmaker_group_id_attribute_get(uint16_t endpoint_id, char *group_id) +{ + ESP_RETURN_ON_FALSE(group_id, ESP_ERR_INVALID_ARG, TAG, "endpoint_url cannot be NULL"); + node_t *node = node::get(); + endpoint_t *endpoint = endpoint::get(node, endpoint_id); + cluster_t *cluster = cluster::get(endpoint, matter_controller::Id); + attribute_t *attribute = esp_matter::attribute::get(cluster, rainmaker_group_id::Id); + ESP_RETURN_ON_FALSE(attribute, ESP_FAIL, TAG, "Could not find group_id attribue"); + esp_matter_attr_val_t raw_val = esp_matter_invalid(NULL); + esp_matter::attribute::get_val(attribute, &raw_val); + ESP_RETURN_ON_FALSE(raw_val.type == ESP_MATTER_VAL_TYPE_CHAR_STRING, ESP_FAIL, TAG, "Invalid Attribute type"); + strncpy(group_id, (const char *)raw_val.val.a.b, raw_val.val.a.s); + group_id[raw_val.val.a.s] = '\0'; + return ESP_OK; +} + +attribute_t *create_rainmaker_group_id(cluster_t *cluster, char *value, uint16_t length) +{ + return esp_matter::attribute::create(cluster, rainmaker_group_id::Id, ATTRIBUTE_FLAG_NONVOLATILE, + esp_matter_char_str(value, length)); +} + +static esp_err_t user_noc_fabric_index_attribute_update(uint16_t endpoint_id, uint8_t fabric_index) +{ + esp_matter_attr_val_t val = esp_matter_uint8(fabric_index); + uint32_t cluster_id = matter_controller::Id; + uint32_t attribute_id = user_noc_fabric_index::Id; + return esp_matter::attribute::update(endpoint_id, cluster_id, attribute_id, &val); +} + +esp_err_t user_noc_fabric_index_attribute_get(uint16_t endpoint_id, uint8_t &fabric_index) +{ + node_t *node = node::get(); + endpoint_t *endpoint = endpoint::get(node, endpoint_id); + cluster_t *cluster = cluster::get(endpoint, matter_controller::Id); + attribute_t *attribute = esp_matter::attribute::get(cluster, user_noc_fabric_index::Id); + ESP_RETURN_ON_FALSE(attribute, ESP_FAIL, TAG, "Could not find group_id attribue"); + esp_matter_attr_val_t raw_val = esp_matter_invalid(NULL); + esp_matter::attribute::get_val(attribute, &raw_val); + ESP_RETURN_ON_FALSE(raw_val.type == ESP_MATTER_VAL_TYPE_UINT8, ESP_FAIL, TAG, "Invalid Attribute type"); + fabric_index = raw_val.val.u8; + return ESP_OK; +} + +attribute_t *create_user_noc_fabric_index(cluster_t *cluster, uint8_t value) +{ + return esp_matter::attribute::create(cluster, user_noc_fabric_index::Id, ATTRIBUTE_FLAG_NONVOLATILE, + esp_matter_uint8(value)); +} + +} // namespace attribute + +namespace command { + +esp_err_t parse_string_from_tlv(TLVReader &tlv_data, ScopedMemoryBufferWithSize &str) +{ + chip::CharSpan str_span; + chip::app::DataModel::Decode(tlv_data, str_span); + ESP_RETURN_ON_FALSE(str_span.data() && str_span.size() > 0, ESP_FAIL, TAG, "Failed to decode the tlv_data"); + strncpy(str.Get(), str_span.data(), str_span.size()); + str[str_span.size()] = '\0'; + return ESP_OK; +} + +static esp_err_t fetch_rainmaker_group_id(uint16_t endpoint_id, ScopedMemoryBufferWithSize &rainmaker_group_id, + uint64_t fabric_id) +{ + esp_err_t ret = ESP_OK; + char url[256] = {0}; + esp_http_client_config_t config = { + .url = url, + .transport_type = HTTP_TRANSPORT_OVER_SSL, + .buffer_size = 1526, + .buffer_size_tx = 2048, + .skip_cert_common_name_check = false, + .crt_bundle_attach = esp_crt_bundle_attach, + }; + esp_http_client_handle_t client = NULL; + ScopedMemoryBufferWithSize endpoint_url; + ScopedMemoryBufferWithSize access_token; + ScopedMemoryBufferWithSize http_payload; + int http_len, http_status_code; + jparse_ctx_t jctx; + int group_count; + int group_index; + char fabric_id_str[17]; + + ESP_RETURN_ON_FALSE(rainmaker_group_id.Get(), ESP_ERR_INVALID_ARG, TAG, "rainmaker_group_id cannot be NULL"); + + endpoint_url.Calloc(ESP_MATTER_RAINMAKER_MAX_ENDPOINT_URL_LEN); + ESP_RETURN_ON_FALSE(endpoint_url.Get(), ESP_ERR_NO_MEM, TAG, "Failed to alloc memory for endpoint_url"); + + access_token.Calloc(ESP_MATTER_RAINMAKER_MAX_ACCESS_TOKEN_LEN); + ESP_RETURN_ON_FALSE(access_token.Get(), ESP_ERR_NO_MEM, TAG, "Failed to alloc memory for access_token"); + + http_payload.Calloc(1024); + ESP_RETURN_ON_FALSE(http_payload.Get(), ESP_ERR_NO_MEM, TAG, "Failed to alloc memory for http_payload"); + + ESP_RETURN_ON_ERROR(attribute::endpoint_url_attribute_get(endpoint_id, endpoint_url.Get()), TAG, + "Failed to get endpoint_url attribute value"); + ESP_RETURN_ON_ERROR(attribute::access_token_attribute_get(endpoint_id, access_token.Get()), TAG, + "Failed to get access_token attribute value"); + + snprintf(url, sizeof(url), "%s/%s/%s?%s", endpoint_url.Get(), HTTP_API_VERSION, "user/node_group", + "node_list=false&sub_groups=false&node_details=false&is_matter=true&fabric_details=false"); + client = esp_http_client_init(&config); + ESP_RETURN_ON_FALSE(client, ESP_FAIL, TAG, "Failed to initialise HTTP Client."); + + ESP_GOTO_ON_ERROR(esp_http_client_set_header(client, "accept", "application/json"), cleanup, TAG, + "Failed to set http header accept"); + ESP_GOTO_ON_ERROR(esp_http_client_set_header(client, "Authorization", access_token.Get()), cleanup, TAG, + "Failed to set http header Authorization"); + ESP_GOTO_ON_ERROR(esp_http_client_set_method(client, HTTP_METHOD_GET), cleanup, TAG, "Failed to set http method"); + + // HTTP GET + ESP_GOTO_ON_ERROR(esp_http_client_open(client, 0), cleanup, TAG, "Failed to open http connection"); + + // Read response + http_len = esp_http_client_fetch_headers(client); + http_status_code = esp_http_client_get_status_code(client); + if ((http_len > 0) && (http_status_code == 200)) { + http_len = esp_http_client_read_response(client, http_payload.Get(), http_payload.AllocatedSize()); + http_payload[http_len] = '\0'; + } else { + http_len = esp_http_client_read_response(client, http_payload.Get(), http_payload.AllocatedSize()); + http_payload[http_len] = '\0'; + ESP_LOGE(TAG, "Invalid response for %s", url); + ESP_LOGE(TAG, "Status = %d, Data = %s", http_status_code, http_len > 0 ? http_payload.Get() : "None"); + ret = ESP_FAIL; + goto close; + } + // Parse the response payload + ESP_GOTO_ON_FALSE(json_parse_start(&jctx, http_payload.Get(), http_len) == 0, ESP_FAIL, close, TAG, + "Failed to parse the http response json on json_parse_start"); + if (json_obj_get_array(&jctx, "groups", &group_count) != 0) { + ESP_LOGE(TAG, "Failed to parse the groups array from the http response"); + json_parse_end(&jctx); + ret = ESP_FAIL; + goto close; + } + + for (group_index = 0; group_index < group_count; ++group_index) { + if (json_arr_get_object(&jctx, group_index) == 0) { + if (json_obj_get_string(&jctx, "fabric_id", fabric_id_str, sizeof(fabric_id_str)) == 0) { + if (strtoull(fabric_id_str, NULL, 16) == fabric_id) { + if (json_obj_get_string(&jctx, "group_id", rainmaker_group_id.Get(), + rainmaker_group_id.AllocatedSize()) != 0) { + ESP_LOGE(TAG, "Failed to parse the group_id for fabric: 0x%llu", fabric_id); + } + json_arr_leave_object(&jctx); + break; + } + } + json_arr_leave_object(&jctx); + } + } + json_obj_leave_array(&jctx); + json_parse_end(&jctx); + + if (group_index == group_count) { + ret = ESP_ERR_NOT_FOUND; + } + +close: + esp_http_client_close(client); +cleanup: + esp_http_client_cleanup(client); + return ret; +} + +#define PEM_BEGIN_CSR "-----BEGIN CERTIFICATE REQUEST-----\n" +#define PEM_END_CSR "-----END CERTIFICATE REQUEST-----\n" + +static esp_err_t generate_user_noc_csr(ScopedMemoryBufferWithSize &csr_pem, uint8_t fabric_index, size_t &out_len) +{ + uint8_t csr_der_buf[chip::Crypto::kMAX_CSR_Length]; + MutableByteSpan csr_span(csr_der_buf); + chip::Server::GetInstance().GetFabricTable().AllocatePendingOperationalKey(chip::MakeOptional(fabric_index), + csr_span); + // copy the csr_der to the end of the csr_pem buffer. + memcpy(csr_pem.Get() + csr_pem.AllocatedSize() - csr_span.size(), csr_span.data(), csr_span.size()); + ESP_RETURN_ON_FALSE( + mbedtls_pem_write_buffer(PEM_BEGIN_CSR, PEM_END_CSR, + reinterpret_cast(csr_pem.Get() + csr_pem.AllocatedSize() - csr_span.size()), + csr_span.size(), reinterpret_cast(csr_pem.Get()), csr_pem.AllocatedSize(), + &out_len) == 0, + ESP_FAIL, TAG, "Failed to generate PEM-Encoded CSR"); + + return ESP_OK; +} + +int convert_pem_to_der(const char *input, size_t ilen, unsigned char *output, size_t *olen) +{ + int ret; + const char *s1, *s2, *end = input + ilen; + size_t len = 0; + + s1 = (char *)strstr(input, "-----BEGIN"); + if (s1 == NULL) { + return -1; + } + + s2 = (char *)strstr((char *)input, "-----END"); + if (s2 == NULL) { + return -1; + } + + s1 += 10; + while (s1 < end && *s1 != '-') { + s1++; + } + while (s1 < end && *s1 == '-') { + s1++; + } + if (*s1 == '\r') { + s1++; + } + if (*s1 == '\n') { + s1++; + } + + if (s2 <= s1 || s2 > end) { + return -1; + } + + ret = mbedtls_base64_decode(NULL, 0, &len, (const unsigned char *)s1, s2 - s1); + if (ret == MBEDTLS_ERR_BASE64_INVALID_CHARACTER) { + return ret; + } + + if (len > *olen) { + return -1; + } + + if ((ret = mbedtls_base64_decode(output, len, &len, (const unsigned char *)s1, s2 - s1)) != 0) { + return ret; + } + + *olen = len; + + return 0; +} + +static void format_csr(const char *input, char *output) +{ + while (*input != '\0') { + if (*input != '\n') { + *output = *input; + } else { + *output = '\\', output++; + *output = 'n'; + } + output++; + input++; + } + *output = '\0'; +} + +static void format_noc(const char *input, char *output) +{ + while (*input != '\0') { + if (*input == '\\' && *(input + 1) == 'n') { + *output = '\n'; + input = input + 2; + } else { + *output = *input; + input++; + } + output++; + } + *output = '\0'; +} + +static esp_err_t issue_user_noc_request(ScopedMemoryBufferWithSize &csr_pem, + ScopedMemoryBufferWithSize &rainmaker_group_id, size_t csr_pem_len, + uint8_t fabric_index, uint16_t endpoint_id) +{ + esp_err_t ret = ESP_OK; + char url[256]; + esp_http_client_config_t config = { + .url = url, + .transport_type = HTTP_TRANSPORT_OVER_SSL, + .buffer_size = 1024, + .buffer_size_tx = 2048, + .skip_cert_common_name_check = false, + .crt_bundle_attach = esp_crt_bundle_attach, + }; + esp_http_client_handle_t client = NULL; + json_gen_str_t jstr; + int http_len, http_status_code; + jparse_ctx_t jctx; + int cert_count; + int noc_pem_len; + size_t noc_der_len; + char noc_user_id[17]; + ScopedMemoryBufferWithSize noc_pem; + ScopedMemoryBufferWithSize noc_pem_formatted; + ScopedMemoryBufferWithSize noc_der; + ScopedMemoryBufferWithSize noc_matter_cert; + MutableByteSpan matter_cert_noc; + auto &fabric_table = chip::Server::GetInstance().GetFabricTable(); + + ScopedMemoryBufferWithSize endpoint_url; + ScopedMemoryBufferWithSize access_token; + ScopedMemoryBufferWithSize http_payload; + ScopedMemoryBufferWithSize csr_pem_formatted; + + endpoint_url.Calloc(ESP_MATTER_RAINMAKER_MAX_ENDPOINT_URL_LEN); + ESP_RETURN_ON_FALSE(endpoint_url.Get(), ESP_ERR_NO_MEM, TAG, "Failed to alloc memory for endpoint_url"); + + access_token.Calloc(ESP_MATTER_RAINMAKER_MAX_ACCESS_TOKEN_LEN); + ESP_RETURN_ON_FALSE(access_token.Get(), ESP_ERR_NO_MEM, TAG, "Failed to alloc memory for access_token"); + + http_payload.Calloc(2048); + ESP_RETURN_ON_FALSE(http_payload.Get(), ESP_ERR_NO_MEM, TAG, "Failed to alloc memory for http_payload"); + + csr_pem_formatted.Calloc(csr_pem.AllocatedSize()); + ESP_RETURN_ON_FALSE(csr_pem_formatted.Get(), ESP_ERR_NO_MEM, TAG, "Failed to alloc memory for csr_pem_formatted"); + + format_csr(csr_pem.Get(), csr_pem_formatted.Get()); + + ESP_RETURN_ON_ERROR(attribute::endpoint_url_attribute_get(endpoint_id, endpoint_url.Get()), TAG, + "Failed to get endpoint_url attribute value"); + ESP_RETURN_ON_ERROR(attribute::access_token_attribute_get(endpoint_id, access_token.Get()), TAG, + "Failed to get access_token attribute value"); + + snprintf(url, sizeof(url), "%s/%s/%s", endpoint_url.Get(), HTTP_API_VERSION, "user/node_group"); + client = esp_http_client_init(&config); + ESP_RETURN_ON_FALSE(client, ESP_FAIL, TAG, "Failed to initialise HTTP Client."); + + ESP_GOTO_ON_ERROR(esp_http_client_set_header(client, "accept", "application/json"), cleanup, TAG, + "Failed to set http header accept"); + ESP_GOTO_ON_ERROR(esp_http_client_set_header(client, "Authorization", access_token.Get()), cleanup, TAG, + "Failed to set http header Authorization"); + ESP_GOTO_ON_ERROR(esp_http_client_set_header(client, "Content-Type", "application/json"), cleanup, TAG, + "Failed to set http header Content-Type"); + ESP_GOTO_ON_ERROR(esp_http_client_set_method(client, HTTP_METHOD_PUT), cleanup, TAG, "Failed to set http method"); + + json_gen_str_start(&jstr, http_payload.Get(), http_payload.AllocatedSize(), NULL, NULL); + json_gen_start_object(&jstr); + json_gen_obj_set_string(&jstr, "operation", "add"); + json_gen_push_array(&jstr, "csr_requests"); + json_gen_start_object(&jstr); + json_gen_obj_set_string(&jstr, "csr", csr_pem_formatted.Get()); + json_gen_obj_set_string(&jstr, "group_id", rainmaker_group_id.Get()); + json_gen_end_object(&jstr); + json_gen_pop_array(&jstr); + json_gen_obj_set_string(&jstr, "csr_type", "user"); + json_gen_end_object(&jstr); + json_gen_str_end(&jstr); + ESP_LOGI(TAG, "http write payload: %s", http_payload.Get()); + + // Send POST data + ESP_GOTO_ON_ERROR(esp_http_client_open(client, strlen(http_payload.Get())), cleanup, TAG, + "Failed to open http connection"); + http_len = esp_http_client_write(client, http_payload.Get(), strlen(http_payload.Get())); + if (http_len != strlen(http_payload.Get())) { + ESP_LOGE(TAG, "Failed to write Payload. Returned len = %d.", http_len); + ret = ESP_FAIL; + goto close; + } + http_len = esp_http_client_fetch_headers(client); + http_status_code = esp_http_client_get_status_code(client); + // Read response + if ((http_len > 0) && (http_status_code == 200)) { + http_len = esp_http_client_read_response(client, http_payload.Get(), http_payload.AllocatedSize()); + http_payload[http_len] = '\0'; + } else { + http_len = esp_http_client_read_response(client, http_payload.Get(), http_payload.AllocatedSize()); + http_payload[http_len] = '\0'; + ESP_LOGE(TAG, "Invalid response for %s", url); + ESP_LOGE(TAG, "Status = %d, Data = %s", http_status_code, http_len > 0 ? http_payload.Get() : "None"); + ret = ESP_FAIL; + } + + // Parse http response + noc_pem.Calloc(1024); + ESP_GOTO_ON_FALSE(noc_pem.Get(), ESP_ERR_NO_MEM, close, TAG, "Failed to allocate memory for noc_pem"); + + noc_pem_formatted.Calloc(1024); + ESP_GOTO_ON_FALSE(noc_pem_formatted.Get(), ESP_ERR_NO_MEM, close, TAG, + "Failed to allocate memory for noc_pem_formatted"); + + ESP_GOTO_ON_FALSE(json_parse_start(&jctx, http_payload.Get(), strlen(http_payload.Get())) == 0, ESP_FAIL, close, + TAG, "Failed to parse the http response json on json_parse_start"); + if (json_obj_get_array(&jctx, "certificates", &cert_count) == 0 && cert_count == 1) { + if (json_arr_get_object(&jctx, 0) == 0) { + if (json_obj_get_strlen(&jctx, "user_noc", &noc_pem_len) == 0 && + json_obj_get_string(&jctx, "user_noc", noc_pem.Get(), noc_pem.AllocatedSize()) == 0) { + noc_pem[noc_pem_len] = '\0'; + if (json_obj_get_string(&jctx, "matter_user_id", noc_user_id, 17) == 0) { + ESP_LOGI(TAG, "NOC user id : 0x%s", noc_user_id); + } + } + json_arr_leave_object(&jctx); + } + json_obj_leave_array(&jctx); + } + json_parse_end(&jctx); + + format_noc(noc_pem.Get(), noc_pem_formatted.Get()); + noc_pem.Free(); + + ESP_LOGD(TAG, "new noc_pem :\n%s", noc_pem_formatted.Get()); + + noc_der.Calloc(chip::Credentials::kMaxDERCertLength); + ESP_GOTO_ON_FALSE(noc_der.Get(), ESP_ERR_NO_MEM, close, TAG, "Failed to allocate memory for noc_der"); + noc_der_len = chip::Credentials::kMaxDERCertLength; + + // Convert PEM-encoded NOC to DER-encoded NOC + ESP_GOTO_ON_FALSE(convert_pem_to_der(noc_pem_formatted.Get(), + strnlen(noc_pem_formatted.Get(), noc_pem_formatted.AllocatedSize()), + noc_der.Get(), &noc_der_len) == 0, + ESP_FAIL, close, TAG, "Failed to convert PEM-encoded NOC to DER-encoded NOC"); + noc_pem_formatted.Free(); + + noc_matter_cert.Calloc(chip::Credentials::kMaxCHIPCertLength); + ESP_GOTO_ON_FALSE(noc_matter_cert.Get(), ESP_ERR_NO_MEM, close, TAG, + "Failed to allocate memory for noc_matter_cert"); + + matter_cert_noc = MutableByteSpan(noc_matter_cert.Get(), noc_matter_cert.AllocatedSize()); + chip::Credentials::ConvertX509CertToChipCert( + ByteSpan{reinterpret_cast(noc_der.Get()), noc_der_len}, matter_cert_noc); + + // Update NOC + ESP_GOTO_ON_FALSE(fabric_table.UpdatePendingFabricWithOperationalKeystore(fabric_index, matter_cert_noc, + ByteSpan{}) == CHIP_NO_ERROR, + ESP_FAIL, close, TAG, "Failed to update the Fabric NOC"); + ESP_GOTO_ON_FALSE(fabric_table.CommitPendingFabricData() == CHIP_NO_ERROR, ESP_FAIL, close, TAG, + "Failed to commit the pending Fabric data"); + // Start DNS server to advertise the new node-id of the new NOC + chip::app::DnssdServer::Instance().StartServer(); + +close: + esp_http_client_close(client); +cleanup: + esp_http_client_cleanup(client); + return ret; +} + +static esp_err_t install_user_noc(uint16_t endpoint_id, uint8_t fabric_index) +{ + const chip::FabricInfo *fabric_info = + chip::Server::GetInstance().GetFabricTable().FindFabricWithIndex(fabric_index); + uint64_t fabric_id = fabric_info->GetFabricId(); + ScopedMemoryBufferWithSize rainmaker_group_id; + ScopedMemoryBufferWithSize csr_pem; + size_t csr_pem_len = 1024; + + // Alloc memory for ScopedMemoryBuffers + rainmaker_group_id.Calloc(ESP_MATTER_RAINMAKER_MAX_GROUP_ID_LEN); + ESP_RETURN_ON_FALSE(rainmaker_group_id.Get(), ESP_ERR_NO_MEM, TAG, "Failed to alloc memory for rainmaker_group_id"); + csr_pem.Calloc(csr_pem_len); + ESP_RETURN_ON_FALSE(csr_pem.Get(), ESP_ERR_NO_MEM, TAG, "Failed to alloc memory for csr_buf"); + + // Fetch ther rainmaker_group_id + ESP_RETURN_ON_ERROR(fetch_rainmaker_group_id(endpoint_id, rainmaker_group_id, fabric_id), TAG, + "Failed to fetch rainmaker_group_id"); + // Update the rainmaker_group_id + ESP_RETURN_ON_ERROR(attribute::rainmaker_group_id_attribute_update( + endpoint_id, rainmaker_group_id.Get(), + strnlen(rainmaker_group_id.Get(), rainmaker_group_id.AllocatedSize())), + TAG, "Failed to update rainmaker_group_id"); + // Generate CSR for User NOC + ESP_RETURN_ON_ERROR(generate_user_noc_csr(csr_pem, fabric_index, csr_pem_len), TAG, + "Failed to generate User NOC CSR"); + // Request to issue User NOC + ESP_RETURN_ON_ERROR(issue_user_noc_request(csr_pem, rainmaker_group_id, csr_pem_len, fabric_index, endpoint_id), + TAG, "Failed to request to issue User NOC"); + // Update attribute + ESP_RETURN_ON_ERROR(attribute::user_noc_installed_attribute_update(endpoint_id, true), TAG, + "Failed to update user_noc_installed"); + ESP_RETURN_ON_ERROR(attribute::user_noc_fabric_index_attribute_update(endpoint_id, fabric_index), TAG, + "Failed to update user_noc_fabric_index"); + // Update controller fabric index + esp_matter::controller::set_fabric_index(fabric_index); + + return ESP_OK; +} + +static esp_err_t append_refresh_token_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; + + // Return if this is not the controller authorize command + if (cluster_id != matter_controller::Id || command_id != matter_controller::command::append_refresh_token::Id) { + ESP_LOGE(TAG, "Got matter_controller command callback for some other command. This should not happen."); + return ESP_FAIL; + } + + ScopedMemoryBufferWithSize append_str; + append_str.Calloc(1025); + ESP_RETURN_ON_FALSE(append_str.Get(), ESP_ERR_NO_MEM, TAG, "Failed to allocate memory for append_str"); + ESP_RETURN_ON_ERROR(parse_string_from_tlv(tlv_data, append_str), TAG, + "Failed to parse appended_refresh_token from tlv_data"); + + ScopedMemoryBufferWithSize refresh_token; + refresh_token.Calloc(ESP_MATTER_RAINMAKER_MAX_REFRESH_TOKEN_LEN); + ESP_RETURN_ON_FALSE(refresh_token.Get(), ESP_ERR_NO_MEM, TAG, "Failed to allocate memory for refresh_token"); + ESP_RETURN_ON_ERROR(attribute::refresh_token_attribute_get(endpoint_id, refresh_token.Get()), TAG, + "Failed to get refresh_token"); + + size_t current_len = strnlen(refresh_token.Get(), ESP_MATTER_RAINMAKER_MAX_REFRESH_TOKEN_LEN); + size_t append_len = strnlen(append_str.Get(), 1024); + strncpy(&refresh_token[current_len], append_str.Get(), append_len); + refresh_token[current_len + append_len] = '\0'; + ESP_RETURN_ON_ERROR( + attribute::refresh_token_attribute_update(endpoint_id, refresh_token.Get(), current_len + append_len), TAG, + "Failed to update refresh_token"); + return ESP_OK; +} + +static esp_err_t reset_refresh_token_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; + + // Return if this is not the controller authorize command + if (cluster_id != matter_controller::Id || command_id != matter_controller::command::reset_refresh_token::Id) { + ESP_LOGE(TAG, "Got matter_controller command callback for some other command. This should not happen."); + return ESP_FAIL; + } + char empty_str[1] = ""; + attribute::refresh_token_attribute_update(endpoint_id, empty_str, sizeof(empty_str)); + return ESP_OK; +} + +static esp_err_t authorize_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; + chip::app::CommandHandler *command_obj = (chip::app::CommandHandler *)opaque_ptr; + + // Return if this is not the controller authorize command + if (cluster_id != matter_controller::Id || command_id != matter_controller::command::authorize::Id) { + ESP_LOGE(TAG, "Got matter_controller command callback for some other command. This should not happen."); + return ESP_FAIL; + } + ScopedMemoryBufferWithSize endpoint_url; + + // Alloc memory for ScopedMemoryBuffers + endpoint_url.Calloc(ESP_MATTER_RAINMAKER_MAX_ENDPOINT_URL_LEN); + ESP_RETURN_ON_FALSE(endpoint_url.Get(), ESP_ERR_NO_MEM, TAG, "Failed to alloc memory for endpoint_url"); + + // Parse the tlv data + ESP_RETURN_ON_ERROR(parse_string_from_tlv(tlv_data, endpoint_url), TAG, + "Failed to parse authorize command tlv data"); + // Flush acks before really slow work + command_obj->FlushAcksRightAwayOnSlowCommand(); + // Update the endpoint URL + ESP_RETURN_ON_ERROR(attribute::endpoint_url_attribute_update( + endpoint_id, endpoint_url.Get(), strnlen(endpoint_url.Get(), endpoint_url.AllocatedSize())), + TAG, "Failed to update endpoint_url"); + + ESP_RETURN_ON_ERROR(controller::controller_authorize(endpoint_id), TAG, "Failed to authorize the controller"); + + return ESP_OK; +} + +static esp_err_t update_user_noc_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; + chip::app::CommandHandler *command_obj = (chip::app::CommandHandler *)opaque_ptr; + uint8_t fabric_index = command_obj->GetAccessingFabricIndex(); + + // Return if this is not the controller authorize command + if (cluster_id != matter_controller::Id || command_id != matter_controller::command::update_user_noc::Id) { + ESP_LOGE(TAG, "Got matter_controller command callback for some other command. This should not happen."); + return ESP_FAIL; + } + + // Get the authorized + bool authorized = false; + ESP_RETURN_ON_ERROR(attribute::authorized_attribute_get(endpoint_id, authorized), TAG, + "Failed to get authorized attribute"); + if (!authorized) { + return ESP_ERR_INVALID_STATE; + } + + // Flush acks before really slow work + command_obj->FlushAcksRightAwayOnSlowCommand(); + // Install user NOC + ESP_RETURN_ON_ERROR(install_user_noc(endpoint_id, fabric_index), TAG, "Failed to install user NOC"); + + return ESP_OK; +} + +static esp_err_t update_device_list_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; + chip::app::CommandHandler *command_obj = (chip::app::CommandHandler *)opaque_ptr; + + // Return if this is not the controller authorize command + if (cluster_id != matter_controller::Id || command_id != matter_controller::command::update_device_list::Id) { + ESP_LOGE(TAG, "Got matter_controller command callback for some other command. This should not happen."); + return ESP_FAIL; + } + // Flush acks before really slow work + command_obj->FlushAcksRightAwayOnSlowCommand(); + + ESP_RETURN_ON_ERROR(controller::device_mgr::update_device_list(endpoint_id), TAG, "Failed to update device list"); + + return ESP_OK; +} + +command_t *create_append_refresh_token(cluster_t *cluster) +{ + return esp_matter::command::create(cluster, append_refresh_token::Id, COMMAND_FLAG_ACCEPTED | COMMAND_FLAG_CUSTOM, + append_refresh_token_command_callback); +} + +command_t *create_reset_refresh_token(cluster_t *cluster) +{ + return esp_matter::command::create(cluster, reset_refresh_token::Id, COMMAND_FLAG_ACCEPTED | COMMAND_FLAG_CUSTOM, + reset_refresh_token_command_callback); +} + +command_t *create_authorize(cluster_t *cluster) +{ + return esp_matter::command::create(cluster, authorize::Id, COMMAND_FLAG_ACCEPTED | COMMAND_FLAG_CUSTOM, + authorize_command_callback); +} + +command_t *create_update_user_noc(cluster_t *cluster) +{ + return esp_matter::command::create(cluster, update_user_noc::Id, COMMAND_FLAG_ACCEPTED | COMMAND_FLAG_CUSTOM, + update_user_noc_command_callback); +} + +command_t *create_update_device_list(cluster_t *cluster) +{ + return esp_matter::command::create(cluster, update_device_list::Id, COMMAND_FLAG_ACCEPTED | COMMAND_FLAG_CUSTOM, + update_device_list_command_callback); +} + +} // namespace command + +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, NULL); + add_function_list(cluster, NULL, 0); + + global::attribute::create_cluster_revision(cluster, 1); + global::attribute::create_feature_map(cluster, 0); + { + char *_refresh_token = (char *)calloc(ESP_MATTER_RAINMAKER_MAX_REFRESH_TOKEN_LEN, sizeof(char)); + attribute::create_refresh_token(cluster, _refresh_token, ESP_MATTER_RAINMAKER_MAX_REFRESH_TOKEN_LEN); + free(_refresh_token); + } + { + char *_access_token = (char *)calloc(ESP_MATTER_RAINMAKER_MAX_ACCESS_TOKEN_LEN, sizeof(char)); + attribute::create_access_token(cluster, _access_token, ESP_MATTER_RAINMAKER_MAX_ACCESS_TOKEN_LEN); + free(_access_token); + } + attribute::create_authorized(cluster, false); + attribute::create_user_noc_installed(cluster, false); + { + char _endpoint_url[ESP_MATTER_RAINMAKER_MAX_ENDPOINT_URL_LEN] = {0}; + attribute::create_endpoint_url(cluster, _endpoint_url, sizeof(_endpoint_url)); + } + { + char _rainmaker_group_id[ESP_MATTER_RAINMAKER_MAX_GROUP_ID_LEN] = {0}; + attribute::create_rainmaker_group_id(cluster, _rainmaker_group_id, sizeof(_rainmaker_group_id)); + } + attribute::create_user_noc_fabric_index(cluster, 0); + + command::create_append_refresh_token(cluster); + command::create_reset_refresh_token(cluster); + command::create_authorize(cluster); + command::create_update_user_noc(cluster); + command::create_update_device_list(cluster); + + return cluster; +} + +} // namespace matter_controller +} // namespace cluster +namespace controller { + +static esp_err_t fetch_access_token(ScopedMemoryBufferWithSize &refresh_token, + ScopedMemoryBufferWithSize &endpoint_url, + ScopedMemoryBufferWithSize &access_token) +{ + esp_err_t ret = ESP_OK; + char url[100]; + snprintf(url, sizeof(url), "%s/%s/%s", endpoint_url.Get(), HTTP_API_VERSION, "login2"); + esp_http_client_config_t config = { + .url = url, + .transport_type = HTTP_TRANSPORT_OVER_SSL, + .buffer_size = 2048, + .skip_cert_common_name_check = false, + .crt_bundle_attach = esp_crt_bundle_attach, + }; + esp_http_client_handle_t client = NULL; + ScopedMemoryBufferWithSize http_payload; + constexpr size_t http_payload_size = + ESP_MATTER_RAINMAKER_MAX_REFRESH_TOKEN_LEN + ESP_MATTER_RAINMAKER_MAX_ACCESS_TOKEN_LEN + 256; + int http_len, http_status_code; + json_gen_str_t jstr; + jparse_ctx_t jctx; + + ESP_RETURN_ON_FALSE(access_token.Get(), ESP_ERR_INVALID_ARG, TAG, "access_token pointer cannot be NULL"); + client = esp_http_client_init(&config); + ESP_RETURN_ON_FALSE(client, ESP_FAIL, TAG, "Failed to initialise HTTP Client."); + + ESP_GOTO_ON_ERROR(esp_http_client_set_header(client, "accept", "application/json"), cleanup, TAG, + "Failed to set http header accept"); + ESP_GOTO_ON_ERROR(esp_http_client_set_header(client, "Content-Type", "application/json"), cleanup, TAG, + "Failed to set http header Content-Type"); + ESP_GOTO_ON_ERROR(esp_http_client_set_method(client, HTTP_METHOD_POST), cleanup, TAG, "Failed to set http method"); + + // Prepare the payload for http write and read + http_payload.Calloc(http_payload_size); + ESP_GOTO_ON_FALSE(http_payload.Get(), ESP_ERR_NO_MEM, cleanup, TAG, "Failed to alloc memory for http_payload"); + json_gen_str_start(&jstr, http_payload.Get(), http_payload.AllocatedSize(), NULL, NULL); + json_gen_start_object(&jstr); + json_gen_obj_set_string(&jstr, "refreshtoken", refresh_token.Get()); + json_gen_end_object(&jstr); + json_gen_str_end(&jstr); + ESP_LOGD(TAG, "http write payload: %s", http_payload.Get()); + + // Send POST data + ESP_GOTO_ON_ERROR(esp_http_client_open(client, strnlen(http_payload.Get(), http_payload_size)), cleanup, TAG, + "Failed to open http connection"); + http_len = esp_http_client_write(client, http_payload.Get(), strnlen(http_payload.Get(), http_payload_size)); + if (http_len != strnlen(http_payload.Get(), http_payload_size)) { + ESP_LOGE(TAG, "Failed to write Payload. Returned len = %d.", http_len); + ret = ESP_FAIL; + goto close; + } + http_len = esp_http_client_fetch_headers(client); + http_status_code = esp_http_client_get_status_code(client); + // Read response + if ((http_len > 0) && (http_status_code == 200)) { + http_len = esp_http_client_read_response(client, http_payload.Get(), http_payload_size); + http_payload[http_len] = '\0'; + } else { + http_len = esp_http_client_read_response(client, http_payload.Get(), http_payload_size); + http_payload[http_len] = '\0'; + ESP_LOGE(TAG, "Invalid response for %s", url); + ESP_LOGE(TAG, "Status = %d, Data = %s", http_status_code, http_len > 0 ? http_payload.Get() : "None"); + ret = ESP_FAIL; + goto close; + } + // Parse the response payload + ESP_GOTO_ON_FALSE(json_parse_start(&jctx, http_payload.Get(), strnlen(http_payload.Get(), http_payload_size)) == 0, + ESP_FAIL, close, TAG, "Failed to parse the http response json on json_parse_start"); + if (json_obj_get_string(&jctx, "accesstoken", access_token.Get(), access_token.AllocatedSize()) != 0) { + ESP_LOGE(TAG, "Failed to parse the access token from the http response json"); + ret = ESP_FAIL; + } + json_parse_end(&jctx); + +close: + esp_http_client_close(client); +cleanup: + esp_http_client_cleanup(client); + return ret; +} + +static uint16_t s_access_token_expired_timer_endpoint_id = chip::kInvalidEndpointId; + +static void access_token_expired_callback(chip::System::Layer *systemLayer, void *appState) +{ + uint16_t endpoint_id = s_access_token_expired_timer_endpoint_id; + if (cluster::matter_controller::attribute::authorized_attribute_update(endpoint_id, false) != ESP_OK) { + ESP_LOGE(TAG, "Failed to update authorized attribute"); + return; + } + if (controller_authorize(endpoint_id) != ESP_OK) { + ESP_LOGE(TAG, "Failed to do authorizing"); + } +} + +esp_err_t controller_authorize(uint16_t endpoint_id) +{ + ScopedMemoryBufferWithSize refresh_token; + ScopedMemoryBufferWithSize endpoint_url; + ScopedMemoryBufferWithSize access_token; + + // Alloc memory for ScopedMemoryBuffers + refresh_token.Calloc(ESP_MATTER_RAINMAKER_MAX_REFRESH_TOKEN_LEN); + ESP_RETURN_ON_FALSE(refresh_token.Get(), ESP_ERR_NO_MEM, TAG, "Failed to alloc memory for refresh_token"); + endpoint_url.Calloc(ESP_MATTER_RAINMAKER_MAX_ENDPOINT_URL_LEN); + ESP_RETURN_ON_FALSE(endpoint_url.Get(), ESP_ERR_NO_MEM, TAG, "Failed to alloc memory for endpoint_url"); + access_token.Calloc(ESP_MATTER_RAINMAKER_MAX_ACCESS_TOKEN_LEN); + ESP_RETURN_ON_FALSE(access_token.Get(), ESP_ERR_NO_MEM, TAG, "Failed to alloc memory for access_token"); + + ESP_RETURN_ON_ERROR( + cluster::matter_controller::attribute::endpoint_url_attribute_get(endpoint_id, endpoint_url.Get()), TAG, + "Failed to get the endpoint_url"); + ESP_RETURN_ON_ERROR( + cluster::matter_controller::attribute::refresh_token_attribute_get(endpoint_id, refresh_token.Get()), TAG, + "Failed to get the refresh_token"); + + // Fetch the access_token + ESP_RETURN_ON_ERROR(fetch_access_token(refresh_token, endpoint_url, access_token), TAG, + "Failed to fetch access_token for authorizing"); + // Update the access token + ESP_RETURN_ON_ERROR(cluster::matter_controller::attribute::access_token_attribute_update( + endpoint_id, access_token.Get(), strnlen(access_token.Get(), access_token.AllocatedSize())), + TAG, "Failed to update access_token"); + // Update the authorized attribute + ESP_RETURN_ON_ERROR(cluster::matter_controller::attribute::authorized_attribute_update(endpoint_id, true), TAG, + "Failed to update authorized attribute"); + // The access token will be expired after one hour + s_access_token_expired_timer_endpoint_id = endpoint_id; + SystemLayer().CancelTimer(access_token_expired_callback, NULL); + SystemLayer().StartTimer(Seconds32(3600), access_token_expired_callback, NULL); + + return ESP_OK; +} + +} // namespace controller +} // namespace esp_matter diff --git a/components/esp_matter_controller/controller_custom_cluster/matter_controller_cluster.h b/components/esp_matter_controller/controller_custom_cluster/matter_controller_cluster.h new file mode 100644 index 000000000..a2c2a5637 --- /dev/null +++ b/components/esp_matter_controller/controller_custom_cluster/matter_controller_cluster.h @@ -0,0 +1,117 @@ +// 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 +#include +#include + +#define ESP_MATTER_RAINMAKER_MAX_ACCESS_TOKEN_LEN 2048 +#define ESP_MATTER_RAINMAKER_MAX_REFRESH_TOKEN_LEN 2048 +#define ESP_MATTER_RAINMAKER_MAX_ENDPOINT_URL_LEN 64 +#define ESP_MATTER_RAINMAKER_MAX_GROUP_ID_LEN 24 +#define HTTP_API_VERSION "v1" + +namespace esp_matter { + +namespace controller { +esp_err_t controller_authorize(uint16_t endpoint_id); +} + +namespace cluster { +namespace matter_controller { + +static constexpr chip::ClusterId Id = 0x131BFC01; + +namespace attribute { + +namespace refresh_token { +static constexpr chip::AttributeId Id = 0x00000000; +} // namespace refresh_token + +namespace access_token { +static constexpr chip::AttributeId Id = 0x00000001; +} // namespace access_token + +namespace authorized { +static constexpr chip::AttributeId Id = 0x00000002; +} // namespace authorized + +namespace user_noc_installed { +static constexpr chip::AttributeId Id = 0x00000003; +} // namespace user_noc_installed + +namespace endpoint_url { +static constexpr chip::AttributeId Id = 0x00000004; +} // namespace endpoint_url + +namespace rainmaker_group_id { +static constexpr chip::AttributeId Id = 0x00000005; +} // namespace rainmaker_group_id + +namespace user_noc_fabric_index { +static constexpr chip::AttributeId Id = 0x00000006; +} // namespace user_noc_fabric_index + +esp_err_t refresh_token_attribute_get(uint16_t endpoint_id, char *refresh_token); +attribute_t *create_refresh_token(cluster_t *cluster, char *value, uint16_t length); +esp_err_t access_token_attribute_get(uint16_t endpoint_id, char *access_token); +attribute_t *create_access_token(cluster_t *cluster, char *value, uint16_t length); +esp_err_t authorized_attribute_update(uint16_t endpoint_id, bool authorized); +esp_err_t authorized_attribute_get(uint16_t endpoint_id, bool &authorized); +attribute_t *create_authorized(cluster_t *cluster, bool value); +esp_err_t user_noc_installed_attribute_get(uint16_t endpoint_id, bool &user_noc_installed); +attribute_t *create_user_noc_installed(cluster_t *cluster, bool value); +esp_err_t endpoint_url_attribute_get(uint16_t endpoint_id, char *endpoint_url); +attribute_t *create_endpoint_url(cluster_t *cluster, char *value, uint16_t length); +esp_err_t rainmaker_group_id_attribute_get(uint16_t endpoint_id, char *group_id); +attribute_t *create_rainmaker_group_id(cluster_t *cluster, char *value, uint16_t length); +esp_err_t user_noc_fabric_index_attribute_get(uint16_t endpoint_id, uint8_t &user_noc_fabric_index); +attribute_t *create_user_noc_fabric_index(cluster_t *cluster, uint8_t user_noc_fabric_index); + +} // namespace attribute + +namespace command { + +namespace append_refresh_token { +static constexpr chip::CommandId Id = 0x00000000; +} // namespace append_refresh_token +command_t *create_append_refresh_token(cluster_t *cluster); + +namespace reset_refresh_token { +static constexpr chip::CommandId Id = 0x00000001; +} // namespace reset_refresh_token +command_t *create_reset_refresh_token(cluster_t *cluster); + +namespace authorize { +static constexpr chip::CommandId Id = 0x00000002; +} // namespace authorize +command_t *create_authorize(cluster_t *cluster); + +namespace update_user_noc { +static constexpr chip::CommandId Id = 0x00000003; +} // namespace update_user_noc +command_t *create_update_user_noc(cluster_t *cluster); + +namespace update_device_list { +static constexpr chip::CommandId Id = 0x00000004; +} // namespace update_device_list +command_t *create_update_device_list(cluster_t *cluster); + +} // namespace command +cluster_t *create(endpoint_t *endpoint, uint8_t flags); +} // namespace matter_controller +} // namespace cluster +} // namespace esp_matter diff --git a/components/esp_matter_controller/controller_custom_cluster/matter_controller_device_mgr.cpp b/components/esp_matter_controller/controller_custom_cluster/matter_controller_device_mgr.cpp new file mode 100644 index 000000000..184bd7bc5 --- /dev/null +++ b/components/esp_matter_controller/controller_custom_cluster/matter_controller_device_mgr.cpp @@ -0,0 +1,520 @@ +// 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 +#include +#include +#include +#include +#include +#include +#include + +#include + +using chip::Platform::ScopedMemoryBufferWithSize; +using namespace esp_matter::cluster::matter_controller::attribute; + +#define TAG "controller_dev_mgr" + +namespace esp_matter { +namespace controller { +namespace device_mgr { + +static matter_device_t *s_matter_device_list = NULL; +static device_list_update_callback_t s_device_list_update_cb = NULL; +static QueueHandle_t s_task_queue = NULL; +static SemaphoreHandle_t s_device_mgr_mutex = NULL; +typedef esp_err_t (*esp_matter_device_mgr_task_t)(void *); + +typedef struct { + esp_matter_device_mgr_task_t task; + void *arg; +} task_post_t; + +class scoped_device_mgr_lock { +public: + scoped_device_mgr_lock() + { + if (s_device_mgr_mutex) { + xSemaphoreTake(s_device_mgr_mutex, portMAX_DELAY); + } else { + ESP_LOGE(TAG, "device mgr lock not initialized"); + } + } + ~scoped_device_mgr_lock() + { + if (s_device_mgr_mutex) { + xSemaphoreGive(s_device_mgr_mutex); + } else { + ESP_LOGE(TAG, "device mgr lock not initialized"); + } + } +}; + +void free_device_list(matter_device_t *dev_list) +{ + matter_device_t *current = dev_list; + while (current) { + dev_list = dev_list->next; + free(current); + current = dev_list; + } +} + +void print_device_list(matter_device_t *dev_list) +{ + matter_device_t *dev = dev_list; + uint8_t dev_index = 0; + while (dev) { + ESP_LOGI(TAG, "device %d : {", dev_index); + ESP_LOGI(TAG, " rainmaker_node_id: %s,", dev->rainmaker_node_id); + ESP_LOGI(TAG, " matter_node_id: 0x%llX,", dev->node_id); + ESP_LOGI(TAG, " is_rainmaker_device: %s,", dev->is_rainmaker_device ? "true" : "false"); + ESP_LOGI(TAG, " is_online: %s,", dev->reachable ? "true" : "false"); + ESP_LOGI(TAG, " endpoints : ["); + for (size_t i = 0; i < dev->endpoint_count; ++i) { + ESP_LOGI(TAG, " {"); + ESP_LOGI(TAG, " endpoint_id: %d,", dev->endpoints[i].endpoint_id); + ESP_LOGI(TAG, " device_type_id: 0x%lx,", dev->endpoints[i].device_type_id); + ESP_LOGI(TAG, " },"); + } + ESP_LOGI(TAG, " ]"); + ESP_LOGI(TAG, "}"); + dev = dev->next; + dev_index++; + } +} + +static matter_device_t *clone_device(matter_device_t *dev) +{ + matter_device_t *ret = (matter_device_t *)malloc(sizeof(matter_device_t)); + if (!ret) { + ESP_LOGE(TAG, "Failed to allocate memory for matter device struct"); + return NULL; + } + memcpy(ret, dev, sizeof(matter_device_t)); + ret->next = NULL; + return ret; +} + +matter_device_t *get_device_list_clone() +{ + matter_device_t *ret = NULL; + scoped_device_mgr_lock dev_mgr_lock; + matter_device_t *current = s_matter_device_list; + while (current) { + matter_device_t *tmp = clone_device(current); + if (!tmp) { + free_device_list(ret); + return NULL; + } + tmp->next = ret; + ret = tmp; + current = current->next; + } + return ret; +} + +static matter_device_t *get_device(uint64_t node_id) +{ + matter_device_t *ret = s_matter_device_list; + while (ret) { + if (ret->node_id == node_id) { + break; + } + ret = ret->next; + } + return ret; +} + +static matter_device_t *get_device(char *rainmaker_node_id) +{ + if (!rainmaker_node_id) { + return NULL; + } + matter_device_t *ret = s_matter_device_list; + while (ret) { + if (strncmp(ret->rainmaker_node_id, rainmaker_node_id, + strnlen(ret->rainmaker_node_id, sizeof(ret->rainmaker_node_id))) == 0) { + break; + } + ret = ret->next; + } + return ret; +} + +matter_device_t *get_device_clone(uint64_t node_id) +{ + return clone_device(get_device(node_id)); +} + +matter_device_t *get_device_clone(char *rainmaker_node_id) +{ + return clone_device(get_device(rainmaker_node_id)); +} + +static esp_err_t get_node_reachable(jparse_ctx_t *jctx, bool *value) +{ + bool value_got = false; + if (json_obj_get_object(jctx, "status") == 0) { + if (json_obj_get_object(jctx, "connectivity") == 0) { + if (json_obj_get_bool(jctx, "connected", value) == 0) { + value_got = true; + } + json_obj_leave_object(jctx); + } + json_obj_leave_object(jctx); + } + return value_got ? ESP_OK : ESP_FAIL; +} + +static esp_err_t get_metadata(jparse_ctx_t *jctx, matter_device_t *dev) +{ + if (json_obj_get_object(jctx, "metadata") == 0) { + int device_type = 0; + if (json_obj_get_int(jctx, "deviceType", &device_type) == 0) { + dev->endpoints[0].device_type_id = device_type; + int ep_count = 0; + int ep_id = 1; + if (json_obj_get_array(jctx, "endpointsData", &ep_count) == 0) { + json_arr_get_int(jctx, 1, &ep_id); + dev->endpoints[0].endpoint_id = ep_id; + json_obj_leave_array(jctx); + } + dev->endpoint_count = 1; + } + json_obj_get_bool(jctx, "isRainmaker", &(dev->is_rainmaker_device)); + json_obj_leave_object(jctx); + } + return ESP_OK; +} + +static esp_err_t fetch_node_metadata(ScopedMemoryBufferWithSize &endpoint_url, + ScopedMemoryBufferWithSize &access_token, uint16_t endpoint_id) +{ + esp_err_t ret = ESP_OK; + matter_device_t *dev = s_matter_device_list; + char url[200]; + int http_len, http_status_code; + esp_http_client_config_t config = { + .url = url, + .transport_type = HTTP_TRANSPORT_OVER_SSL, + .buffer_size = 4096, + .buffer_size_tx = 2048, + .skip_cert_common_name_check = false, + .crt_bundle_attach = esp_crt_bundle_attach, + }; + esp_http_client_handle_t client = NULL; + jparse_ctx_t jctx; + int node_count = 0; + int node_index = 0; + char id_str[24] = {0}; + int id_str_len = 0; + ScopedMemoryBufferWithSize http_payload; + http_payload.Calloc(4096); + ESP_RETURN_ON_FALSE(http_payload.Get(), ESP_ERR_NO_MEM, TAG, "Failed to allocate memory for http_payload"); + + scoped_device_mgr_lock dev_mgr_lock; + while (dev) { + snprintf(url, sizeof(url), "%s/%s/%s?node_id=%s&%s", endpoint_url.Get(), HTTP_API_VERSION, "user/nodes", + dev->rainmaker_node_id, "node_details=true&is_matter=true"); + client = esp_http_client_init(&config); + ESP_RETURN_ON_FALSE(client, ESP_FAIL, TAG, "Failed to initialise HTTP Client."); + + ESP_GOTO_ON_ERROR(esp_http_client_set_header(client, "accept", "application/json"), cleanup, TAG, + "Failed to set http header accept"); + ESP_GOTO_ON_ERROR(esp_http_client_set_header(client, "Authorization", access_token.Get()), cleanup, TAG, + "Failed to set http header Authorization"); + ESP_GOTO_ON_ERROR(esp_http_client_set_method(client, HTTP_METHOD_GET), cleanup, TAG, + "Failed to set http method"); + + // HTTP GET Method + ESP_GOTO_ON_ERROR(esp_http_client_open(client, 0), cleanup, TAG, "Failed to open http connection"); + + http_len = esp_http_client_fetch_headers(client); + http_status_code = esp_http_client_get_status_code(client); + + // Read Response + if ((http_len > 0) && (http_status_code == 200)) { + http_len = esp_http_client_read_response(client, http_payload.Get(), http_payload.AllocatedSize()); + http_payload[http_len] = '\0'; + } else { + http_len = esp_http_client_read_response(client, http_payload.Get(), http_payload.AllocatedSize()); + http_payload[http_len] = '\0'; + ESP_LOGE(TAG, "Invalid response for %s", url); + ESP_LOGE(TAG, "Status = %d, Data = %s", http_status_code, http_len > 0 ? http_payload.Get() : "None"); + if (http_status_code == 401) { + cluster::matter_controller::attribute::authorized_attribute_update(endpoint_id, false); + } + ret = ESP_FAIL; + goto close; + } + ESP_LOGD(TAG, "http response payload: %s", http_payload.Get()); + + // Parse the http response + ESP_GOTO_ON_FALSE(json_parse_start(&jctx, http_payload.Get(), strlen(http_payload.Get())) == 0, ESP_FAIL, close, + TAG, "Failed to parse the http response json on json_parse_start"); + if (json_obj_get_array(&jctx, "node_details", &node_count) == 0) { + for (node_index = 0; node_index < node_count; ++node_index) { + if (json_arr_get_object(&jctx, node_index) == 0) { + if (json_obj_get_strlen(&jctx, "id", &id_str_len) == 0 && + json_obj_get_string(&jctx, "id", id_str, id_str_len + 1) == 0) { + id_str[id_str_len] = '\0'; + dev = get_device(id_str); + if (dev) { + get_node_reachable(&jctx, &(dev->reachable)); + get_metadata(&jctx, dev); + } + } + json_arr_leave_object(&jctx); + } + } + json_obj_leave_array(&jctx); + } + json_parse_end(&jctx); + dev = dev->next; + esp_http_client_close(client); + esp_http_client_cleanup(client); + client = NULL; + } + print_device_list(s_matter_device_list); + +close: + if (client) { + esp_http_client_close(client); + } +cleanup: + if (client) { + esp_http_client_cleanup(client); + } + return ret; +} + +#define CONTROLLER_NODE_TYPE "Controller" + +static esp_err_t fetch_node_list(ScopedMemoryBufferWithSize &endpoint_url, + ScopedMemoryBufferWithSize &access_token, + ScopedMemoryBufferWithSize &rainmaker_group_id, uint16_t endpoint_id) +{ + esp_err_t ret = ESP_OK; + char url[200]; + int http_len, http_status_code; + ScopedMemoryBufferWithSize http_payload; + jparse_ctx_t jctx; + int array_count = 0; + matter_device_t *new_device_list = NULL; + size_t node_index = 0; + char node_type_str[32] = {0}; + int str_len = 0; + + snprintf(url, sizeof(url), "%s/%s/%s=%s&%s", endpoint_url.Get(), HTTP_API_VERSION, "user/node_group?group_id", + rainmaker_group_id.Get(), "node_details=true&sub_groups=false&node_list=true&is_matter=true"); + esp_http_client_config_t config = { + .url = url, + .transport_type = HTTP_TRANSPORT_OVER_SSL, + .buffer_size = 2048, + .buffer_size_tx = 2048, + .skip_cert_common_name_check = false, + .crt_bundle_attach = esp_crt_bundle_attach, + }; + esp_http_client_handle_t client = esp_http_client_init(&config); + ESP_RETURN_ON_FALSE(client, ESP_FAIL, TAG, "Failed to initialise HTTP Client."); + + ESP_GOTO_ON_ERROR(esp_http_client_set_header(client, "accept", "application/json"), cleanup, TAG, + "Failed to set http header accept"); + ESP_GOTO_ON_ERROR(esp_http_client_set_header(client, "Authorization", access_token.Get()), cleanup, TAG, + "Failed to set http header Authorization"); + ESP_GOTO_ON_ERROR(esp_http_client_set_method(client, HTTP_METHOD_GET), cleanup, TAG, "Failed to set http method"); + + // HTTP GET Method + ESP_GOTO_ON_ERROR(esp_http_client_open(client, 0), cleanup, TAG, "Failed to open http connection"); + + http_len = esp_http_client_fetch_headers(client); + http_status_code = esp_http_client_get_status_code(client); + http_payload.Calloc(2048); + ESP_GOTO_ON_FALSE(http_payload.Get(), ESP_ERR_NO_MEM, close, TAG, "Failed to allocate memory for http_payload"); + + // Read Response + if ((http_len > 0) && (http_status_code == 200)) { + http_len = esp_http_client_read_response(client, http_payload.Get(), http_payload.AllocatedSize()); + http_payload[http_len] = '\0'; + } else { + http_len = esp_http_client_read_response(client, http_payload.Get(), http_payload.AllocatedSize()); + http_payload[http_len] = '\0'; + ESP_LOGE(TAG, "Invalid response for %s", url); + ESP_LOGE(TAG, "Status = %d, Data = %s", http_status_code, http_len > 0 ? http_payload.Get() : "None"); + if (http_status_code == 401) { + cluster::matter_controller::attribute::authorized_attribute_update(endpoint_id, false); + } + ret = ESP_FAIL; + goto close; + } + ESP_LOGD(TAG, "http response payload: %s\n", http_payload.Get()); + + // Parse http respnse + ESP_GOTO_ON_FALSE(json_parse_start(&jctx, http_payload.Get(), strlen(http_payload.Get())) == 0, ESP_FAIL, close, + TAG, "Failed to parse the http response json on json_parse_start"); + if (json_obj_get_array(&jctx, "groups", &array_count) == 0 && array_count == 1) { + if (json_arr_get_object(&jctx, 0) == 0) { + if (json_obj_get_array(&jctx, "node_details", &array_count) == 0 && array_count > 0) { + for (node_index = 0; node_index < array_count; ++node_index) { + if (json_arr_get_object(&jctx, node_index) == 0) { + if (json_obj_get_strlen(&jctx, "type", &str_len) == 0 && + json_obj_get_string(&jctx, "type", node_type_str, str_len + 1) == 0) { + if (strncmp(node_type_str, CONTROLLER_NODE_TYPE, strlen(CONTROLLER_NODE_TYPE)) == 0) { + // Skip the controller node + json_arr_leave_object(&jctx); + continue; + } + } + char matter_node_id_str[17]; + if (json_obj_get_strlen(&jctx, "matter_node_id", &str_len) == 0 && + json_obj_get_string(&jctx, "matter_node_id", matter_node_id_str, str_len + 1) == 0) { + matter_node_id_str[str_len] = '\0'; + matter_device_t *device_entry = (matter_device_t *)calloc(1, sizeof(matter_device_t)); + if (!device_entry) { + ESP_LOGE(TAG, "Failed to alloc memory for device entry"); + free_device_list(new_device_list); + ret = ESP_ERR_NO_MEM; + goto close; + } + device_entry->next = new_device_list; + new_device_list = device_entry; + device_entry->node_id = strtoull(matter_node_id_str, NULL, 16); + if (json_obj_get_strlen(&jctx, "node_id", &str_len) == 0) { + json_obj_get_string(&jctx, "node_id", device_entry->rainmaker_node_id, str_len + 1); + } + } + json_arr_leave_object(&jctx); + } + } + json_obj_leave_array(&jctx); + } + json_arr_leave_object(&jctx); + } + json_obj_leave_array(&jctx); + } + json_parse_end(&jctx); + { + scoped_device_mgr_lock dev_mgr_lock; + free_device_list(s_matter_device_list); + s_matter_device_list = new_device_list; + } + +close: + esp_http_client_close(client); +cleanup: + esp_http_client_cleanup(client); + return ret; +} + +static esp_err_t update_device_list_task(void *endpoint_id_ptr) +{ + ScopedMemoryBufferWithSize endpoint_url; + ScopedMemoryBufferWithSize rainmaker_group_id; + ScopedMemoryBufferWithSize access_token; + uint16_t endpoint_id = *(uint16_t *)endpoint_id_ptr; + free(endpoint_id_ptr); + + endpoint_url.Calloc(ESP_MATTER_RAINMAKER_MAX_ENDPOINT_URL_LEN); + ESP_RETURN_ON_FALSE(endpoint_url.Get(), ESP_ERR_NO_MEM, TAG, "Failed to alloc memory for endpoint_url"); + rainmaker_group_id.Calloc(ESP_MATTER_RAINMAKER_MAX_GROUP_ID_LEN); + ESP_RETURN_ON_FALSE(rainmaker_group_id.Get(), ESP_ERR_NO_MEM, TAG, "Failed to alloc memory for rainmaker_group_id"); + access_token.Calloc(ESP_MATTER_RAINMAKER_MAX_ACCESS_TOKEN_LEN); + ESP_RETURN_ON_FALSE(access_token.Get(), ESP_ERR_NO_MEM, TAG, "Failed to alloc memory for access_token"); + + ESP_RETURN_ON_ERROR(endpoint_url_attribute_get(endpoint_id, endpoint_url.Get()), TAG, + "Failed to get the endpoint_url"); + ESP_RETURN_ON_ERROR(rainmaker_group_id_attribute_get(endpoint_id, rainmaker_group_id.Get()), TAG, + "Failed to get the rainmaker_group_id"); + ESP_RETURN_ON_ERROR(access_token_attribute_get(endpoint_id, access_token.Get()), TAG, + "Failed to get the access_token"); + + ESP_RETURN_ON_ERROR(fetch_node_list(endpoint_url, access_token, rainmaker_group_id, endpoint_id), TAG, + "Failed to fetch the node list"); + ESP_RETURN_ON_ERROR(fetch_node_metadata(endpoint_url, access_token, endpoint_id), TAG, + "Failed to fetch the node metadata"); + if (s_device_list_update_cb) { + s_device_list_update_cb(); + } + return ESP_OK; +} + +esp_err_t update_device_list(uint16_t endpoint_id) +{ + uint16_t *endpoint_id_ptr = (uint16_t *)malloc(sizeof(uint16_t)); + *endpoint_id_ptr = endpoint_id; + task_post_t task_post = { + .task = update_device_list_task, + .arg = endpoint_id_ptr, + }; + if (xQueueSend(s_task_queue, &task_post, portMAX_DELAY) != pdTRUE) { + free(endpoint_id_ptr); + ESP_LOGE(TAG, "Failed send update device list task"); + return ESP_FAIL; + } + return ESP_OK; +} + +static void device_mgr_task(void *aContext) +{ + s_task_queue = xQueueCreate(8 /* Queue Size */, sizeof(task_post_t)); + if (!s_task_queue) { + ESP_LOGE(TAG, "Failed to create device mgr task queue"); + return; + } + s_device_mgr_mutex = xSemaphoreCreateRecursiveMutex(); + if (!s_device_mgr_mutex) { + ESP_LOGE(TAG, "Failed to create device mgr lock"); + vQueueDelete(s_task_queue); + return; + } + task_post_t task_post; + while (true) { + if (xQueueReceive(s_task_queue, &task_post, portMAX_DELAY) == pdTRUE) { + task_post.task(task_post.arg); + } + } + vQueueDelete(s_task_queue); + vSemaphoreDelete(s_device_mgr_mutex); + vTaskDelete(NULL); +} + +esp_err_t init(uint16_t endpoint_id, device_list_update_callback_t dev_list_update_cb) +{ + uint8_t fabric_index; + bool user_noc_installed = false; + s_device_list_update_cb = dev_list_update_cb; + if (xTaskCreate(device_mgr_task, "device_mgr", 4096, NULL, 5, NULL) != pdTRUE) { + ESP_LOGE(TAG, "Failed to create device mgr task"); + return ESP_ERR_NO_MEM; + } + + ESP_RETURN_ON_ERROR(user_noc_installed_attribute_get(endpoint_id, user_noc_installed), TAG, + "Failed to get the user_noc_installed"); + if (user_noc_installed) { + // get the user_noc_fabric_index and pass it to the controller. + cluster::matter_controller::attribute::user_noc_fabric_index_attribute_get(endpoint_id, fabric_index); + esp_matter::controller::set_fabric_index(fabric_index); + // Do authorizing before update the device list + ESP_RETURN_ON_ERROR(controller_authorize(endpoint_id), TAG, "Failed to do authorizing"); + ESP_RETURN_ON_ERROR(controller::device_mgr::update_device_list(endpoint_id), TAG, + "Failed to update device list"); + } + return ESP_OK; +} +} // namespace device_mgr +} // namespace controller +} // namespace esp_matter diff --git a/components/esp_matter_controller/controller_custom_cluster/matter_controller_device_mgr.h b/components/esp_matter_controller/controller_custom_cluster/matter_controller_device_mgr.h new file mode 100644 index 000000000..bd0c08e5e --- /dev/null +++ b/components/esp_matter_controller/controller_custom_cluster/matter_controller_device_mgr.h @@ -0,0 +1,57 @@ +// 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 + +namespace esp_matter { +namespace controller { +namespace device_mgr { + +#define ESP_MATTER_DEVICE_MAX_ENDPOINT 8 + +typedef struct endpoint_entry { + uint16_t endpoint_id; + uint32_t device_type_id; +} endpoint_entry_t; + +typedef struct matter_device { + uint64_t node_id; + char rainmaker_node_id[24]; + uint8_t endpoint_count; + endpoint_entry_t endpoints[ESP_MATTER_DEVICE_MAX_ENDPOINT]; + bool reachable; + bool is_rainmaker_device; + struct matter_device *next; +} matter_device_t; + +typedef void (*device_list_update_callback_t)(void); + +void free_device_list(matter_device_t *dev_list); + +void print_device_list(matter_device_t *dev_list); + +matter_device_t *get_device_list_clone(); + +matter_device_t *get_device_clone(uint64_t node_id); + +matter_device_t *get_device_clone(char *rainmaker_node_id); + +esp_err_t update_device_list(uint16_t endpoint_id); + +esp_err_t init(uint16_t endpoint_id, device_list_update_callback_t dev_list_update_cb); +} // namespace device_mgr +} // namespace controller +} // namespace esp_matter diff --git a/components/esp_matter_controller/esp_matter_controller_cluster_command.cpp b/components/esp_matter_controller/esp_matter_controller_cluster_command.cpp index 70e76ba16..d377cddb0 100644 --- a/components/esp_matter_controller/esp_matter_controller_cluster_command.cpp +++ b/components/esp_matter_controller/esp_matter_controller_cluster_command.cpp @@ -60,7 +60,7 @@ static esp_err_t send_group_command(command_data_t *command_data, uint16_t group #if CONFIG_ESP_MATTER_COMMISSIONER_ENABLE uint8_t fabric_index = commissioner::get_device_commissioner()->GetFabricIndex(); #else - uint8_t fabric_index = 1; + uint8_t fabric_index = get_fabric_index(); #endif switch (command_data->command_id) { case OnOff::Commands::On::Id: @@ -371,7 +371,8 @@ static esp_err_t send_command(command_data_t *command_data, peer_device_t *remot } // namespace clusters -void cluster_command::on_device_connected_fcn(void *context, ExchangeManager &exchangeMgr, const SessionHandle &sessionHandle) +void cluster_command::on_device_connected_fcn(void *context, ExchangeManager &exchangeMgr, + const SessionHandle &sessionHandle) { cluster_command *cmd = reinterpret_cast(context); chip::OperationalDeviceProxy device_proxy(&exchangeMgr, sessionHandle); @@ -432,7 +433,7 @@ esp_err_t cluster_command::send_command() } #else chip::Server *server = &(chip::Server::GetInstance()); - server->GetCASESessionManager()->FindOrEstablishSession(ScopedNodeId(m_destination_id, /* fabric index */ 1), + server->GetCASESessionManager()->FindOrEstablishSession(ScopedNodeId(m_destination_id, get_fabric_index()), &on_device_connected_cb, &on_device_connection_failure_cb); return ESP_OK; #endif diff --git a/components/esp_matter_controller/esp_matter_controller_read_command.cpp b/components/esp_matter_controller/esp_matter_controller_read_command.cpp index eb63f6031..7589dc06c 100644 --- a/components/esp_matter_controller/esp_matter_controller_read_command.cpp +++ b/components/esp_matter_controller/esp_matter_controller_read_command.cpp @@ -87,7 +87,7 @@ esp_err_t read_command::send_command() } #else chip::Server *server = &(chip::Server::GetInstance()); - server->GetCASESessionManager()->FindOrEstablishSession(ScopedNodeId(m_node_id, /* fabric index */ 1), + server->GetCASESessionManager()->FindOrEstablishSession(ScopedNodeId(m_node_id, get_fabric_index()), &on_device_connected_cb, &on_device_connection_failure_cb); return ESP_OK; #endif diff --git a/components/esp_matter_controller/esp_matter_controller_subscribe_command.cpp b/components/esp_matter_controller/esp_matter_controller_subscribe_command.cpp index cafff3592..ac53bb6c5 100644 --- a/components/esp_matter_controller/esp_matter_controller_subscribe_command.cpp +++ b/components/esp_matter_controller/esp_matter_controller_subscribe_command.cpp @@ -94,7 +94,7 @@ esp_err_t subscribe_command::send_command() } #else chip::Server *server = &(chip::Server::GetInstance()); - server->GetCASESessionManager()->FindOrEstablishSession(ScopedNodeId(m_node_id, /* fabric index */ 1), + server->GetCASESessionManager()->FindOrEstablishSession(ScopedNodeId(m_node_id, get_fabric_index()), &on_device_connected_cb, &on_device_connection_failure_cb); return ESP_OK; #endif diff --git a/components/esp_matter_controller/esp_matter_controller_utils.cpp b/components/esp_matter_controller/esp_matter_controller_utils.cpp index ee72b2474..6856e86df 100644 --- a/components/esp_matter_controller/esp_matter_controller_utils.cpp +++ b/components/esp_matter_controller/esp_matter_controller_utils.cpp @@ -15,6 +15,25 @@ #include #include +namespace esp_matter { +namespace controller { + +#if !CONFIG_ESP_MATTER_COMMISSIONER_ENABLE +uint8_t s_controller_fabric_index = chip::kUndefinedFabricIndex; + +void set_fabric_index(uint8_t fabric_index) +{ + s_controller_fabric_index = fabric_index; +} + +uint8_t get_fabric_index(void) +{ + return s_controller_fabric_index; +} +#endif // !CONFIG_ESP_MATTER_COMMISSIONER_ENABLE + +} // namespace controller +} // namespace esp_matter static uint8_t char_to_hex_digit(char c) { if (c >= '0' && c <= '9') { diff --git a/components/esp_matter_controller/esp_matter_controller_utils.h b/components/esp_matter_controller/esp_matter_controller_utils.h index 99b49ea52..1dcf2638e 100644 --- a/components/esp_matter_controller/esp_matter_controller_utils.h +++ b/components/esp_matter_controller/esp_matter_controller_utils.h @@ -29,6 +29,22 @@ using event_report_cb_t = void (*)(uint64_t remote_node_id, const chip::app::Eve using subscribe_done_cb_t = void (*)(uint64_t remote_node_id); using subscribe_failure_cb_t = void (*)(void *subscribe_command); +#if !CONFIG_ESP_MATTER_COMMISSIONER_ENABLE +/** + * @brief Set the fabric index of the controller. + * The controller should be able to send commands to the devices on this fabric. + * + * This should be called after the controller is added to the fabric. + */ +void set_fabric_index(uint8_t fabric_index); + +/** + * @brief Get the fabric index of the controller. + * + */ +uint8_t get_fabric_index(); +#endif // !CONFIG_ESP_MATTER_COMMISSIONER_ENABLE + } // namespace controller } // namespace esp_matter diff --git a/components/esp_matter_controller/esp_matter_controller_write_command.cpp b/components/esp_matter_controller/esp_matter_controller_write_command.cpp index 63964eb4a..6471aba60 100644 --- a/components/esp_matter_controller/esp_matter_controller_write_command.cpp +++ b/components/esp_matter_controller/esp_matter_controller_write_command.cpp @@ -81,7 +81,7 @@ esp_err_t write_command::send_command() } #else chip::Server *server = &(chip::Server::GetInstance()); - server->GetCASESessionManager()->FindOrEstablishSession(ScopedNodeId(m_node_id, /* fabric index */ 1), + server->GetCASESessionManager()->FindOrEstablishSession(ScopedNodeId(m_node_id, get_fabric_index()), &on_device_connected_cb, &on_device_connection_failure_cb); return ESP_OK; #endif @@ -249,7 +249,8 @@ static esp_err_t parse_acl_json(char *json_str, acl_attr_t *acl, size_t *acl_siz "Failed to get targets from the ACL json string: Error on targets length"); for (size_t targ_index = 0; targ_index < targets_num; ++targ_index) { ESP_RETURN_ON_FALSE(json_arr_get_object(&jctx, targ_index) == 0, ESP_ERR_INVALID_ARG, TAG, - "Failed to get targets from the ACL json string: Error on targets-%u value", targ_index); + "Failed to get targets from the ACL json string: Error on targets-%u value", + targ_index); int64_t cluster_val, device_type_val; int endpoint_val; bool exist_cluster, exist_endpoint, exist_device_type; diff --git a/components/esp_matter_controller/idf_component.yml b/components/esp_matter_controller/idf_component.yml index 58f130c72..ca8d04bc7 100644 --- a/components/esp_matter_controller/idf_component.yml +++ b/components/esp_matter_controller/idf_component.yml @@ -1,3 +1,4 @@ ## IDF Component Manager Manifest File dependencies: espressif/json_parser: "1.0.0" + espressif/json_generator: "1.1.0" diff --git a/examples/controller/README.md b/examples/controller/README.md index 83dd903f1..ad6d7601d 100644 --- a/examples/controller/README.md +++ b/examples/controller/README.md @@ -14,13 +14,17 @@ No additional setup is required. See the [docs](https://docs.espressif.com/projects/esp-matter/en/latest/esp32/developing.html#controller-example) for more information about pairing and controling an end-device using this example +## 3. Controller in Rainmaker Fabric + +Matter Controller in Rainmaker Fabric will be soon avaliable in [Rainmaker Matter Examples](https://github.com/espressif/esp-rainmaker/tree/master/examples/matter) + ## A2 Appendix FAQs ### A2.1 Pairing Command Failed I cannot finish the commissioning on the controller example: -- Currently this example only supports onnetwork pairing, so please make sure that the end-device and the controller are on the same IP-network. +- For onnetwork pairing, please make sure that the end-device and the controller are on the same IP-network. - The controller uses the hard-code test PAA certification so the PAI and DAC on the end-device should be generated by the [test cert](https://github.com/espressif/connectedhomeip/blob/4f7669b052b16bd054227376e1bbadac85419793/credentials/test/attestation/Chip-Test-PAA-NoVID-Cert.pem) and the [test key](https://github.com/espressif/connectedhomeip/blob/4f7669b052b16bd054227376e1bbadac85419793/credentials/test/attestation/Chip-Test-PAA-NoVID-Key.pem) - If you are still facing issues, reproduce the issue on the default example for the device and then raise an [issue](https://github.com/espressif/esp-matter/issues). Make sure to share these: - The complete device logs for both the devices taken over UART. diff --git a/examples/controller/main/app_main.cpp b/examples/controller/main/app_main.cpp index bb175eba7..71f0b8995 100644 --- a/examples/controller/main/app_main.cpp +++ b/examples/controller/main/app_main.cpp @@ -12,12 +12,16 @@ #include #include +#include #include #include #include #include +#include +#include + static const char *TAG = "app_main"; uint16_t switch_endpoint_id = 0; @@ -31,7 +35,13 @@ static void app_event_cb(const ChipDeviceEvent *event, intptr_t arg) case chip::DeviceLayer::DeviceEventType::PublicEventTypes::kInterfaceIpAddressChanged: ESP_LOGI(TAG, "Interface IP Address changed"); break; - +#if !CONFIG_ESP_MATTER_COMMISSIONER_ENABLE + case chip::DeviceLayer::DeviceEventType::kFabricCommitted: + if (chip::Server::GetInstance().GetFabricTable().FindFabricWithIndex(1)) { + esp_matter::controller::set_fabric_index(1); + } + break; +#endif default: break; }