diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 81ba6a5db..9065e984b 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -83,7 +83,7 @@ build_esp_matter_examples: - *build_examples - *build_bridge_zigbee_app -build_all_examples: +build_upstream_examples: stage: build image: $CI_DOCKER_REGISTRY/esp32-ci-env:matter tags: @@ -97,4 +97,3 @@ build_all_examples: - *setup_idf - *setup_matter - *build_matter_examples - - *build_examples diff --git a/components/esp_matter/esp_matter_attribute.cpp b/components/esp_matter/esp_matter_attribute.cpp index 4e207e1ec..473963fa0 100644 --- a/components/esp_matter/esp_matter_attribute.cpp +++ b/components/esp_matter/esp_matter_attribute.cpp @@ -916,34 +916,34 @@ static esp_err_t get_attr_val_from_data(esp_matter_attr_val_t *val, EmberAfAttri void esp_matter_attribute_val_print(int endpoint_id, int cluster_id, int attribute_id, esp_matter_attr_val_t *val) { if (val->type == ESP_MATTER_VAL_TYPE_BOOLEAN) { - ESP_LOGI(TAG, "********** Endpoint 0x%04X's Cluster 0x%04X's Attribute 0x%04X is %d **********", endpoint_id, + ESP_LOGI(TAG, "********** Endpoint 0x%04X's Cluster 0x%08X's Attribute 0x%04X is %d **********", endpoint_id, cluster_id, attribute_id, val->val.b); } else if (val->type == ESP_MATTER_VAL_TYPE_INTEGER) { - ESP_LOGI(TAG, "********** Endpoint 0x%04X's Cluster 0x%04X's Attribute 0x%04X is %d **********", endpoint_id, + ESP_LOGI(TAG, "********** Endpoint 0x%04X's Cluster 0x%08X's Attribute 0x%04X is %d **********", endpoint_id, cluster_id, attribute_id, val->val.i); } else if (val->type == ESP_MATTER_VAL_TYPE_FLOAT) { - ESP_LOGI(TAG, "********** Endpoint 0x%04X's Cluster 0x%04X's Attribute 0x%04X is %f **********", endpoint_id, + ESP_LOGI(TAG, "********** Endpoint 0x%04X's Cluster 0x%08X's Attribute 0x%04X is %f **********", endpoint_id, cluster_id, attribute_id, val->val.f); } else if (val->type == ESP_MATTER_VAL_TYPE_UINT8 || val->type == ESP_MATTER_VAL_TYPE_BITMAP8) { - ESP_LOGI(TAG, "********** Endpoint 0x%04X's Cluster 0x%04X's Attribute 0x%04X is %d **********", endpoint_id, + ESP_LOGI(TAG, "********** Endpoint 0x%04X's Cluster 0x%08X's Attribute 0x%04X is %d **********", endpoint_id, cluster_id, attribute_id, val->val.u8); } else if (val->type == ESP_MATTER_VAL_TYPE_INT16) { - ESP_LOGI(TAG, "********** Endpoint 0x%04X's Cluster 0x%04X's Attribute 0x%04X is %d **********", endpoint_id, + ESP_LOGI(TAG, "********** Endpoint 0x%04X's Cluster 0x%08X's Attribute 0x%04X is %d **********", endpoint_id, cluster_id, attribute_id, val->val.i16); } else if (val->type == ESP_MATTER_VAL_TYPE_UINT16) { - ESP_LOGI(TAG, "********** Endpoint 0x%04X's Cluster 0x%04X's Attribute 0x%04X is %d **********", endpoint_id, + ESP_LOGI(TAG, "********** Endpoint 0x%04X's Cluster 0x%08X's Attribute 0x%04X is %d **********", endpoint_id, cluster_id, attribute_id, val->val.u16); } else if (val->type == ESP_MATTER_VAL_TYPE_UINT32) { - ESP_LOGI(TAG, "********** Endpoint 0x%04X's Cluster 0x%04X's Attribute 0x%04X is %d **********", endpoint_id, + ESP_LOGI(TAG, "********** Endpoint 0x%04X's Cluster 0x%08X's Attribute 0x%04X is %d **********", endpoint_id, cluster_id, attribute_id, val->val.u32); } else if (val->type == ESP_MATTER_VAL_TYPE_UINT64) { - ESP_LOGI(TAG, "********** Endpoint 0x%04X's Cluster 0x%04X's Attribute 0x%04X is %lld **********", endpoint_id, + ESP_LOGI(TAG, "********** Endpoint 0x%04X's Cluster 0x%08X's Attribute 0x%04X is %lld **********", endpoint_id, cluster_id, attribute_id, val->val.u64); } else if (val->type == ESP_MATTER_VAL_TYPE_CHAR_STRING) { - ESP_LOGI(TAG, "********** Endpoint 0x%04X's Cluster 0x%04X's Attribute 0x%04X is %.*s **********", endpoint_id, + ESP_LOGI(TAG, "********** Endpoint 0x%04X's Cluster 0x%08X's Attribute 0x%04X is %.*s **********", endpoint_id, cluster_id, attribute_id, val->val.a.s, val->val.a.b); } else { - ESP_LOGI(TAG, "********** Endpoint 0x%04X's Cluster 0x%04X's Attribute 0x%04X is **********", + ESP_LOGI(TAG, "********** Endpoint 0x%04X's Cluster 0x%08X's Attribute 0x%04X is **********", endpoint_id, cluster_id, attribute_id, val->type); } } diff --git a/components/esp_matter/esp_matter_command.cpp b/components/esp_matter/esp_matter_command.cpp index 2b23c6650..c1cd84b10 100644 --- a/components/esp_matter/esp_matter_command.cpp +++ b/components/esp_matter/esp_matter_command.cpp @@ -52,7 +52,7 @@ void DispatchSingleClusterCommandCommon(const ConcreteCommandPath &command_path, int endpoint_id = command_path.mEndpointId; int cluster_id = command_path.mClusterId; int command_id = command_path.mCommandId; - ESP_LOGI(TAG, "Received command 0x%04X for enpoint 0x%04X's cluster 0x%04X", command_id, endpoint_id, cluster_id); + ESP_LOGI(TAG, "Received command 0x%04X for enpoint 0x%04X's cluster 0x%08X", command_id, endpoint_id, cluster_id); esp_matter_node_t *node = esp_matter_node_get(); esp_matter_endpoint_t *endpoint = esp_matter_endpoint_get(node, endpoint_id); diff --git a/components/esp_matter/esp_matter_core.cpp b/components/esp_matter/esp_matter_core.cpp index cb809dbc8..a8d05f712 100644 --- a/components/esp_matter/esp_matter_core.cpp +++ b/components/esp_matter/esp_matter_core.cpp @@ -120,7 +120,7 @@ static int esp_matter_command_get_count(_esp_matter_command_t *current, int comm { int count = 0; while (current) { - if (current->flags &= command_flag) { + if (current->flags & command_flag) { count++; } current = current->next; @@ -297,7 +297,7 @@ esp_err_t esp_matter_endpoint_enable(esp_matter_endpoint_t *endpoint) break; } while (command) { - if (command->flags &= command_flag) { + if (command->flags & command_flag) { client_generated_command_ids[command_index] = command->command_id; command_index++; } @@ -319,7 +319,7 @@ esp_err_t esp_matter_endpoint_enable(esp_matter_endpoint_t *endpoint) break; } while (command) { - if (command->flags &= command_flag) { + if (command->flags & command_flag) { server_generated_command_ids[command_index] = command->command_id; command_index++; } diff --git a/examples/rainmaker_light/README.md b/examples/rainmaker_light/README.md index 1a5b721a6..ebd29f0c6 100644 --- a/examples/rainmaker_light/README.md +++ b/examples/rainmaker_light/README.md @@ -35,7 +35,7 @@ export ESP_RMAKER_PATH=/path/to/esp-rainmaker ### RainMaker Claiming -This need to be done before flashing the firmware. Note the mac address of the device. +If self-claiming is not enabled/supported, this need to be done before flashing the firmware. RainMaker CLI: ``` @@ -45,19 +45,34 @@ $ rainmaker.py claim --addr 0x3E0000 $ESPPORT ### RainMaker User-Node Association -This need to be done after commissioning. +This needs to be done after commissioning. + +Check if the device already has user node association done, using the custom RainMaker cluster (cluster_id: 0xc00): +``` +$ ./out/debug/chip-tool any read-by-id 0xc00 0x0 0x1 0x0 +``` +* If the above custom status attribute (attribute_id: 0x0) returns true, the association has already been done. +* If the attribute returns false, the association has not been done. And the below custom configuration command +(command_id: 0x0) can be used to do the association. + RainMaker CLI: + +Get the details: This will print the user_id and secret_key (do not close this): ``` $ rainmaker.py test --addnode + +>> add-user ``` -This will print the console command to be run on the device: +Prepare the command payload: Use the above details. ``` -add-user +payload: :: ``` -Use these details in the below command on the device console. +Now use the payload to run the RainMaker configuration command from chip-tool: ``` -matter esp rainmaker add-user +$ ./out/debug/chip-tool any command-by-id 0xc00 0x0 '"::"' 0x1 0x0 ``` + +The device/node should now be associated with the user. diff --git a/examples/rainmaker_light/main/app_main.cpp b/examples/rainmaker_light/main/app_main.cpp index f4764da24..4aacbe746 100644 --- a/examples/rainmaker_light/main/app_main.cpp +++ b/examples/rainmaker_light/main/app_main.cpp @@ -55,6 +55,14 @@ static esp_err_t app_attribute_update_cb(esp_matter_callback_type_t type, int en return err; } +esp_err_t app_command_callback(int endpoint_id, int cluster_id, int command_id, TLVReader &tlv_data, void *priv_data) +{ + esp_err_t err = ESP_OK; + /* Pass all the commands to all the ecosystems, if their command callbacks exist */ + err = app_rainmaker_command_callback(endpoint_id, cluster_id, command_id, tlv_data, priv_data); + return err; +} + extern "C" void app_main() { esp_err_t err = ESP_OK; @@ -81,6 +89,10 @@ extern "C" void app_main() /* Initialize driver */ app_driver_init(); + /* Initialize rainmaker */ + esp_matter_command_set_custom_callback(app_command_callback, NULL); + app_rainmaker_init(); + /* Matter start */ err = esp_matter_start(app_event_cb); if (err != ESP_OK) { @@ -88,8 +100,8 @@ extern "C" void app_main() } app_qrcode_print(); - /* Initialize rainmaker */ - app_rainmaker_init(); + /* Start rainmaker */ + app_rainmaker_start(); #if CONFIG_ENABLE_CHIP_SHELL esp_matter_console_diagnostics_register_commands(); diff --git a/examples/rainmaker_light/main/app_rainmaker.cpp b/examples/rainmaker_light/main/app_rainmaker.cpp index 7b893930b..988cbe45d 100644 --- a/examples/rainmaker_light/main/app_rainmaker.cpp +++ b/examples/rainmaker_light/main/app_rainmaker.cpp @@ -334,7 +334,124 @@ static void app_rainmaker_device_create() } } +#define ESP_MATTER_RAINMAKER_ENDPOINT_ID 0x0 /* Same as root node endpoint. This will always be endpoint_id 0. */ +#define ESP_MATTER_RAINMAKER_CLUSTER_ID 0x131B0000 /* 0x131B == manufacturer code */ +#define ESP_MATTER_RAINMAKER_STATUS_ATTRIBUTE_ID 0x0 +#define ESP_MATTER_RAINMAKER_CONFIGURATION_COMMAND_ID 0x0 +#define ESP_MATTER_RAINMAKER_CLUSTER_REVISION 1 +#define ESP_MATTER_RAINMAKER_COMMAND_LIMIT 5 /* This command can be called 5 times per reboot */ +#define ESP_MATTER_RAINMAKER_MAX_DATA_LEN 40 + +static esp_err_t app_rainmaker_status_attribute_update(bool status) +{ + int endpoint_id = ESP_MATTER_RAINMAKER_ENDPOINT_ID; + int cluster_id = ESP_MATTER_RAINMAKER_CLUSTER_ID; + int attribute_id = ESP_MATTER_RAINMAKER_STATUS_ATTRIBUTE_ID; + esp_matter_attr_val_t val = esp_matter_bool(status); + return esp_matter_attribute_update(endpoint_id, cluster_id, attribute_id, &val); +} + +static void app_rainmaker_user_node_association_event_handler(void* arg, esp_event_base_t event_base, int32_t event_id, + void* event_data) +{ + /* This event handler is only for user node association status */ + if (event_base == RMAKER_EVENT) { + if (event_id == RMAKER_EVENT_USER_NODE_MAPPING_DONE) { + ESP_LOGI(TAG, "User node association complete. Updating the status attribute."); + app_rainmaker_status_attribute_update(true); + } else if (event_id == RMAKER_EVENT_USER_NODE_MAPPING_RESET) { + ESP_LOGI(TAG, "User node association reset. Updating the status attribute."); + app_rainmaker_status_attribute_update(false); + } + } +} + +esp_err_t app_rainmaker_command_callback(int endpoint_id, int cluster_id, int command_id, TLVReader &tlv_data, + void *priv_data) +{ + /* Return if this is not the rainmaker configuration command */ + if (endpoint_id != ESP_MATTER_RAINMAKER_ENDPOINT_ID || cluster_id != ESP_MATTER_RAINMAKER_CLUSTER_ID + || command_id != ESP_MATTER_RAINMAKER_CONFIGURATION_COMMAND_ID) { + return ESP_OK; + } + ESP_LOGI(TAG, "RainMaker configuration command callback"); + static int command_count = ESP_MATTER_RAINMAKER_COMMAND_LIMIT; + if (command_count <= 0) { + ESP_LOGE(TAG, "This command has reached a limit. Please reboot to try again."); + return ESP_FAIL; + } + command_count--; + + /* Parse the tlv data */ + chip::CharSpan config_value; + chip::app::DataModel::Decode(tlv_data, config_value); + const char *data = config_value.data(); + int size = config_value.size(); + + /* The expected format of the data is "::" */ + char ch = ':'; + char *check = strchr(data, (int)ch); + if (check == NULL) { + ESP_LOGE(TAG, "':' not found in the received data: %.*s. The expected format is \"::\"", + size, data); + return ESP_FAIL; + } + + /* Get sizes */ + int user_id_index = 0; + int user_id_len = (int)(strchr(data, (int)ch) - data); /* (first ':') - (start of string) */ + int secret_key_index = (int)(strrchr(data, (int)ch) - data) + 1; /* (last ':') - (start of string) + 1 */ + int secret_key_len = size - secret_key_index; + if (user_id_len <= 0 || user_id_len >= ESP_MATTER_RAINMAKER_MAX_DATA_LEN || secret_key_len <= 0 + || secret_key_len >= ESP_MATTER_RAINMAKER_MAX_DATA_LEN) { + ESP_LOGE(TAG, "User id or secret key length invalid: user_id_len: %d, secret_key_len: %d", user_id_len, + secret_key_len); + return ESP_FAIL; + } + + /* Copy the data. This done to make the strings NULL terminated. */ + char user_id[ESP_MATTER_RAINMAKER_MAX_DATA_LEN] = {0}; + char secret_key[ESP_MATTER_RAINMAKER_MAX_DATA_LEN] = {0}; + strncpy(user_id, &data[user_id_index], user_id_len); + strncpy(secret_key, &data[secret_key_index], secret_key_len); + ESP_LOGI(TAG, "user_id: %s, secret_key: %s", user_id, secret_key); + + /* Call the rainmaker API */ + if (strlen(user_id) > 0 && strlen(secret_key) > 0) { + esp_rmaker_start_user_node_mapping(user_id, secret_key); + } + return ESP_OK; +} + +esp_err_t app_rainmaker_custom_cluster_create() +{ + /* Get the endpoint */ + esp_matter_node_t *node = esp_matter_node_get(); + esp_matter_endpoint_t *endpoint = esp_matter_endpoint_get(node, ESP_MATTER_RAINMAKER_ENDPOINT_ID); + + /* Create custom rainmaker cluster */ + esp_matter_cluster_t *cluster = esp_matter_cluster_create(endpoint, ESP_MATTER_RAINMAKER_CLUSTER_ID, ESP_MATTER_CLUSTER_FLAG_SERVER); + esp_matter_attribute_create(cluster, ZCL_CLUSTER_REVISION_SERVER_ATTRIBUTE_ID, ESP_MATTER_ATTRIBUTE_FLAG_NONE, + esp_matter_uint16(ESP_MATTER_RAINMAKER_CLUSTER_REVISION)); + + /* Create custom status attribute */ + /* Update the value of the attribute after esp_rmaker_node_init() is done */ + esp_matter_attribute_create(cluster, ESP_MATTER_RAINMAKER_STATUS_ATTRIBUTE_ID, ESP_MATTER_ATTRIBUTE_FLAG_NONE, + esp_matter_bool(false)); + + /* Create custom configuration command */ + esp_matter_command_create(cluster, ESP_MATTER_RAINMAKER_CONFIGURATION_COMMAND_ID, + ESP_MATTER_COMMAND_FLAG_CLIENT_GENERATED | ESP_MATTER_COMMAND_FLAG_CUSTOM, NULL); + return ESP_OK; +} + esp_err_t app_rainmaker_init() +{ + /* Add custom rainmaker cluster */ + return app_rainmaker_custom_cluster_create(); +} + +esp_err_t app_rainmaker_start() { /* Initialize the ESP RainMaker Agent. * Note that this should be called after app_wifi_init() but before app_wifi_start() @@ -368,6 +485,16 @@ esp_err_t app_rainmaker_init() /* Enable scheduling. */ esp_rmaker_schedule_enable(); + /* Check user node association */ + if (esp_rmaker_user_node_mapping_get_state() == ESP_RMAKER_USER_MAPPING_DONE) { + app_rainmaker_status_attribute_update(true); + } + /* Register an event handler and update the state later */ + esp_event_handler_register(RMAKER_EVENT, RMAKER_EVENT_USER_NODE_MAPPING_DONE, + &app_rainmaker_user_node_association_event_handler, NULL); + esp_event_handler_register(RMAKER_EVENT, RMAKER_EVENT_USER_NODE_MAPPING_RESET, + &app_rainmaker_user_node_association_event_handler, NULL); + /* Start the ESP RainMaker Agent */ esp_rmaker_start(); diff --git a/examples/rainmaker_light/main/app_rainmaker.h b/examples/rainmaker_light/main/app_rainmaker.h index 05d41e67c..0b319d045 100644 --- a/examples/rainmaker_light/main/app_rainmaker.h +++ b/examples/rainmaker_light/main/app_rainmaker.h @@ -17,16 +17,28 @@ extern "C" { /** Initialize ESP RainMaker * - * This initializes the devices and params for RainMaker, corresponding to the endpoint and attributes. It also adds - * RainMaker features like OTA, Scheduling, etc. + * This adds the custom RainMaker cluster. * * @return ESP_OK on success. * @return error in case of failure. */ esp_err_t app_rainmaker_init(void); +/** Start ESP RainMaker + * + * This initializes the devices and params for RainMaker, corresponding to the endpoint and attributes. It also adds + * RainMaker features like OTA, Scheduling, etc. + * + * @return ESP_OK on success. + * @return error in case of failure. + */ +esp_err_t app_rainmaker_start(void); + esp_err_t app_rainmaker_attribute_update(int endpoint_id, int cluster_id, int attribute_id, esp_matter_attr_val_t *val); +esp_err_t app_rainmaker_command_callback(int endpoint_id, int cluster_id, int command_id, TLVReader &tlv_data, + void *priv_data); + #ifdef __cplusplus } #endif diff --git a/examples/rainmaker_light/sdkconfig.defaults b/examples/rainmaker_light/sdkconfig.defaults index 42631b422..91a9702bf 100644 --- a/examples/rainmaker_light/sdkconfig.defaults +++ b/examples/rainmaker_light/sdkconfig.defaults @@ -33,3 +33,7 @@ CONFIG_FREERTOS_TIMER_TASK_STACK_DEPTH=3120 #enable lwIP route hooks CONFIG_LWIP_HOOK_IP6_ROUTE_DEFAULT=y CONFIG_LWIP_HOOK_ND6_GET_GW_DEFAULT=y + +# ESP RainMaker +CONFIG_ESP_RMAKER_USER_ID_CHECK=y +CONFIG_ESP_RMAKER_SELF_CLAIM=y diff --git a/examples/rainmaker_light/sdkconfig.defaults.esp32s2 b/examples/rainmaker_light/sdkconfig.defaults.esp32s2 index 10be192a6..9ddd524d5 100644 --- a/examples/rainmaker_light/sdkconfig.defaults.esp32s2 +++ b/examples/rainmaker_light/sdkconfig.defaults.esp32s2 @@ -30,5 +30,8 @@ CONFIG_FREERTOS_TIMER_TASK_STACK_DEPTH=3120 CONFIG_LWIP_HOOK_IP6_ROUTE_DEFAULT=y CONFIG_LWIP_HOOK_ND6_GET_GW_DEFAULT=y +# ESP RainMaker +CONFIG_ESP_RMAKER_USER_ID_CHECK=y + # Disable BLE CONFIG_ENABLE_CHIPOBLE=n