From ad94862cc4ec51cecfa8d656c3770e9b03aa416b Mon Sep 17 00:00:00 2001 From: WanqQixiang Date: Wed, 7 Sep 2022 15:28:15 +0800 Subject: [PATCH] client: add command_handle to store the command information in client command send Example: Add group binding for light-switch example --- components/esp_matter/esp_matter_client.cpp | 100 +++++++++++++++++--- components/esp_matter/esp_matter_client.h | 3 + components/esp_matter/esp_matter_core.h | 50 ++++++++-- examples/light_switch/README.md | 52 +++++++++- examples/light_switch/main/app_driver.cpp | 81 ++++++++++------ examples/light_switch/main/app_main.cpp | 4 + 6 files changed, 236 insertions(+), 54 deletions(-) diff --git a/components/esp_matter/esp_matter_client.cpp b/components/esp_matter/esp_matter_client.cpp index d7caf4574..ecae36999 100644 --- a/components/esp_matter/esp_matter_client.cpp +++ b/components/esp_matter/esp_matter_client.cpp @@ -37,38 +37,56 @@ namespace esp_matter { namespace client { static command_callback_t client_command_callback = NULL; -static void *client_command_callback_priv_data = NULL; +static group_command_callback_t client_group_command_callback = NULL; +static void *command_callback_priv_data; static bool initialize_binding_manager = false; -esp_err_t set_command_callback(command_callback_t callback, void *priv_data) +esp_err_t set_command_callback(command_callback_t callback, group_command_callback_t g_callback, void *priv_data) { client_command_callback = callback; - client_command_callback_priv_data = priv_data; + client_group_command_callback = g_callback; + command_callback_priv_data = priv_data; return ESP_OK; } -/** TODO: Change g_remote_endpoint_id to something better. */ -uint16_t g_remote_endpoint_id = kInvalidEndpointId; void esp_matter_connection_success_callback(void *context, ExchangeManager & exchangeMgr, SessionHandle & sessionHandle) { + command_handle_t *cmd_handle = static_cast(context); + if (!cmd_handle) { + ESP_LOGE(TAG, "Failed to call connect_success_callback since the command handle is NULL"); + return; + } ESP_LOGI(TAG, "New connection success"); + // Only unicast binding needs to establish the connection if (client_command_callback) { OperationalDeviceProxy device(&exchangeMgr, sessionHandle); - client_command_callback(&device, g_remote_endpoint_id, client_command_callback_priv_data); + client_command_callback(&device, cmd_handle->endpoint_id, cmd_handle, command_callback_priv_data); } + chip::Platform::Delete(cmd_handle); } void esp_matter_connection_failure_callback(void *context, const ScopedNodeId & peerId, CHIP_ERROR error) { + command_handle_t *cmd_handle = static_cast(context); ESP_LOGI(TAG, "New connection failure"); + if (cmd_handle) { + chip::Platform::Delete(cmd_handle); + } } -esp_err_t connect(uint8_t fabric_index, uint64_t node_id, uint16_t remote_endpoint_id) +esp_err_t connect(uint8_t fabric_index, uint64_t node_id, command_handle_t *cmd_handle) { static Callback success_callback(esp_matter_connection_success_callback, NULL); static Callback failure_callback(esp_matter_connection_failure_callback, NULL); + + command_handle_t *context = chip::Platform::New(cmd_handle); + if (!context) { + ESP_LOGE(TAG, "failed to alloc memory for the command handle"); + return ESP_ERR_NO_MEM; + } + success_callback.mContext = static_cast(context); + failure_callback.mContext = static_cast(context); Server * server = &(chip::Server::GetInstance()); - g_remote_endpoint_id = remote_endpoint_id; server->GetCASESessionManager()->FindOrEstablishSession(ScopedNodeId(node_id, fabric_index), &success_callback, &failure_callback); return ESP_OK; @@ -77,14 +95,44 @@ esp_err_t connect(uint8_t fabric_index, uint64_t node_id, uint16_t remote_endpoi static void esp_matter_command_client_binding_callback(const EmberBindingTableEntry &binding, OperationalDeviceProxy *peer_device, void *context) { - if (client_command_callback) { - client_command_callback(peer_device, binding.remote, client_command_callback_priv_data); + command_handle_t *cmd_handle = static_cast(context); + if (!cmd_handle) { + ESP_LOGE(TAG, "Failed to call the binding callback since command handle is NULL"); + return; + } + if (binding.type == EMBER_UNICAST_BINDING && !cmd_handle->is_group && peer_device) { + if (client_command_callback) { + client_command_callback(peer_device, binding.remote, cmd_handle, command_callback_priv_data); + } + } else if (binding.type == EMBER_MULTICAST_BINDING && cmd_handle->is_group && !peer_device) { + if (client_group_command_callback) { + client_group_command_callback(binding.fabricIndex, binding.groupId, cmd_handle, command_callback_priv_data); + } } } -esp_err_t cluster_update(uint16_t endpoint_id, uint32_t cluster_id) +static void esp_matter_binding_context_release(void *context) { - chip::BindingManager::GetInstance().NotifyBoundClusterChanged(endpoint_id, cluster_id, NULL); + if (context) { + chip::Platform::Delete(static_cast(context)); + } +} + +esp_err_t cluster_update(uint16_t local_endpoint_id, command_handle_t *cmd_handle) +{ + command_handle_t *context = chip::Platform::New(cmd_handle); + if (!context) { + ESP_LOGE(TAG, "failed to alloc memory for the command handle"); + return ESP_ERR_NO_MEM; + } + if (CHIP_NO_ERROR != + chip::BindingManager::GetInstance().NotifyBoundClusterChanged(local_endpoint_id, cmd_handle->cluster_id, + static_cast(context))) { + chip::Platform::Delete(context); + ESP_LOGE(TAG, "failed to notify the bound cluster changed"); + return ESP_FAIL; + } + return ESP_OK; } @@ -99,6 +147,7 @@ static void __binding_manager_init(intptr_t arg) chip::BindingManager::GetInstance().Init(binding_init_params); chip::BindingManager::GetInstance().RegisterBoundDeviceChangedHandler(esp_matter_command_client_binding_callback); + chip::BindingManager::GetInstance().RegisterBoundDeviceContextReleaseHandler(esp_matter_binding_context_release); } void binding_manager_init() @@ -139,6 +188,15 @@ esp_err_t send_on(peer_device_t *remote_device, uint16_t remote_endpoint_id) return ESP_OK; } +esp_err_t group_send_on(uint8_t fabric_index, uint16_t group_id) +{ + OnOff::Commands::On::Type command_data; + chip::Messaging::ExchangeManager & exchange_mgr = chip::Server::GetInstance().GetExchangeManager(); + + chip::Controller::InvokeGroupCommandRequest(&exchange_mgr, fabric_index, group_id, command_data); + return ESP_OK; +} + esp_err_t send_off(peer_device_t *remote_device, uint16_t remote_endpoint_id) { OnOff::Commands::Off::Type command_data; @@ -148,6 +206,15 @@ esp_err_t send_off(peer_device_t *remote_device, uint16_t remote_endpoint_id) return ESP_OK; } +esp_err_t group_send_off(uint8_t fabric_index, uint16_t group_id) +{ + OnOff::Commands::Off::Type command_data; + chip::Messaging::ExchangeManager & exchange_mgr = chip::Server::GetInstance().GetExchangeManager(); + + chip::Controller::InvokeGroupCommandRequest(&exchange_mgr, fabric_index, group_id, command_data); + return ESP_OK; +} + esp_err_t send_toggle(peer_device_t *remote_device, uint16_t remote_endpoint_id) { OnOff::Commands::Toggle::Type command_data; @@ -157,6 +224,15 @@ esp_err_t send_toggle(peer_device_t *remote_device, uint16_t remote_endpoint_id) return ESP_OK; } +esp_err_t group_send_toggle(uint8_t fabric_index, uint16_t group_id) +{ + OnOff::Commands::Toggle::Type command_data; + chip::Messaging::ExchangeManager & exchange_mgr = chip::Server::GetInstance().GetExchangeManager(); + + chip::Controller::InvokeGroupCommandRequest(&exchange_mgr, fabric_index, group_id, command_data); + return ESP_OK; +} + } /* command */ } /* on_off */ diff --git a/components/esp_matter/esp_matter_client.h b/components/esp_matter/esp_matter_client.h index ee516caf2..ef82f39fb 100644 --- a/components/esp_matter/esp_matter_client.h +++ b/components/esp_matter/esp_matter_client.h @@ -31,6 +31,9 @@ namespace command { esp_err_t send_off(peer_device_t *remote_device, uint16_t remote_endpoint_id); esp_err_t send_on(peer_device_t *remote_device, uint16_t remote_endpoint_id); esp_err_t send_toggle(peer_device_t *remote_device, uint16_t remote_endpoint_id); +esp_err_t group_send_off(uint8_t fabric_index, uint16_t group_id); +esp_err_t group_send_on(uint8_t fabric_index, uint16_t group_id); +esp_err_t group_send_toggle(uint8_t fabric_index, uint16_t group_id); } /* command */ } /* on_off */ diff --git a/components/esp_matter/esp_matter_core.h b/components/esp_matter/esp_matter_core.h index dd0e9a905..78fa7e5f8 100644 --- a/components/esp_matter/esp_matter_core.h +++ b/components/esp_matter/esp_matter_core.h @@ -691,6 +691,22 @@ uint16_t get_flags(command_t *command); /* Client APIs */ namespace client { +/** Command handle as the input when calling `connect()` or `cluster_update()` + * + */ + +typedef struct command_handle { + uint16_t endpoint_id; + uint32_t cluster_id; + uint32_t command_id; + void *command_data { NULL }; + bool is_group; + command_handle() : endpoint_id(chip::kInvalidEndpointId), cluster_id(chip::kInvalidClusterId), + command_id(chip::kInvalidCommandId), command_data(NULL), is_group(false) {} + command_handle(struct command_handle* cmd) : endpoint_id(cmd->endpoint_id), cluster_id(cmd->cluster_id), + command_id(cmd->command_id), command_data(cmd->command_data), is_group(cmd->is_group) {} +} command_handle_t; + /** Peer device handle */ typedef chip::DeviceProxy peer_device_t; @@ -701,10 +717,24 @@ typedef chip::DeviceProxy peer_device_t; * * @param[in] peer_device Peer device handle. This can be passed to the send_command APIs. * @param[in] remote_endpoint_id Endpoint ID of the other device. This can be passed to the send_command APIs. + * @param[in] cmd_handle Command handle used by `connect()` or `cluster_update()`. * @param[in] priv_data (Optional) Private data associated with the callback. This will be passed to callback. It * should stay allocated throughout the lifetime of the device. */ -typedef void (*command_callback_t)(peer_device_t *peer_device, uint16_t remote_endpoint_id, void *priv_data); +typedef void (*command_callback_t)(peer_device_t *peer_device, uint16_t remote_endpoint_id, command_handle_t *cmd_handle, + void *priv_data); + +/** Group command send callback + * + * This callback will be called when `cluster_update()` is called and the group command is triggered for binding cluster. + * + * @param[in] fabric_index The index of the fabric that the group command is sent to. + * @param[in] group_id The group_id that the group command is sent to. + * @param[in] cmd_handle Command handle used by `cluster_update()`. + * @param[in] priv_data (Optional) Private data associated with the callback. This will be passed to callback. It + * should stay allocated throughout the lifetime of the device. + */ +typedef void (*group_command_callback_t)(uint8_t fabric_index, uint16_t group_id, command_handle_t *cmd_handle, void *priv_data); /** Initialize binding * @@ -726,39 +756,41 @@ void binding_manager_init(); * * @param[in] fabric_index Fabric index. * @param[in] node_id Node ID of the other device. - * @param[in] remote_endpoint_id Endpoint ID of the other device. + * @param[in] cmd_handle Command to be sent to the remote device. * * @return ESP_OK on success. * @return error in case of failure. */ -esp_err_t connect(uint8_t fabric_index, uint64_t node_id, uint16_t remote_endpoint_id); +esp_err_t connect(uint8_t fabric_index, uint64_t node_id, command_handle_t *cmd_handle); /** Set command send callback * - * Set the common command send callback. This callback will be called when `connect()` or `cluster_update()` is called - * and the connection completes. + * Set the common command send callback and the group command send callback. The common callback will be called + * when `connect()` or `cluster_update()` is called and the connection completes. The group callback will be called + * when `cluster_update()` is called and the group command is triggered. * * @param[in] callback command send callback. + * @param[in] g_callback group command send callback * @param[in] priv_data (Optional) Private data associated with the callback. This will be passed to callback. It * should stay allocated throughout the lifetime of the device. * * @return ESP_OK on success. * @return error in case of failure. */ -esp_err_t set_command_callback(command_callback_t callback, void *priv_data); +esp_err_t set_command_callback(command_callback_t callback, group_command_callback_t g_callback, void *priv_data); /** Cluster update * * For an already binded device, this API can be used to get the command send callback, and the send_command APIs can * then be called from the callback. * - * @param[in] endpoint_id Local endpoint ID of the command. - * @param[in] cluster_id Cluster ID of the command. + * @param[in] local_endpoint_id The ID of the local endpoint with a binding cluster. + * @param[in] cmd_handle Command information to notify the bound cluster changed. * * @return ESP_OK on success. * @return error in case of failure. */ -esp_err_t cluster_update(uint16_t endpoint_id, uint32_t cluster_id); +esp_err_t cluster_update(uint16_t local_endpoint_id, command_handle_t *cmd_handle); } /* client */ } /* esp_matter */ diff --git a/examples/light_switch/README.md b/examples/light_switch/README.md index 3d4e0585d..9f987ba0b 100644 --- a/examples/light_switch/README.md +++ b/examples/light_switch/README.md @@ -39,14 +39,41 @@ Update the switch's binding attribute to add the entry of remote device binding write binding '[{"fabricIndex": 1, "node":20836, "endpoint":1, "cluster":6}]' 0x7283 0x1 ``` -### 2.2 Device console +### 2.2 Bind a group to switch + +Using the chip-tool, commission 3 (or more) devices, 1 switch and 2 (or more) lights. +If you are having trouble, try commissioning them one at a time (by powering off the other device) as +the default discriminator and passcode are same for both of them. +Then use the below commands to add the devices to the group and bind the group to the switch. + +For the commands below: +- Node Id of switch used during commissioning is 0x7283 (29315 in decimal) +- Node Id of light1 used during commissioning is 0x5164 (20836 in decimal) +- Node Id of light2 used during commissioning is 0x5163 (20835 in decimal) +- Group Id for the devices is 257 which is assigned by chip-tool when using the testing-group command +- Binding cluster is currently present on endpoint 1 on the switch + +Send the testing-group command to the switch and lights. +This command will write the acl attributes of the nodes and add the endpoint 1 of the nodes to the group 257. +``` + tests TestGroupDemoConfig --nodeId 29315 + tests TestGroupDemoConfig --nodeId 20836 + tests TestGroupDemoConfig --nodeId 20835 +``` + +Update the switch's binding attribute to add the entry of group in the binding table: +``` + binding write binding '[{"fabricIndex": 1, "group": 257}]' 0x7283 0x1 +``` + +### 2.3 Device console Switch specific console commands: - Send command to all the bound devices on the specified cluster: (The IDs are in hex): ``` - matter esp bound invoke + matter esp bound invoke ``` - Example: Off: @@ -64,6 +91,27 @@ Switch specific console commands: matter esp bound invoke 0x1 0x6 0x2 ``` +- Send command to all the bound groups on the specified cluster: + (The IDs are in hex): + ``` + matter esp bound invoke-group + ``` + + - Example: Off: + ``` + matter esp bound invoke-group 0x1 0x6 0x0 + ``` + + - Example: On: + ``` + matter esp bound invoke-group 0x1 0x6 0x1 + ``` + + - Example: Toggle: + ``` + matter esp bound invoke-group 0x1 0x6 0x2 + ``` + ## 3. Device Performance ### 3.1 Memory usage diff --git a/examples/light_switch/main/app_driver.cpp b/examples/light_switch/main/app_driver.cpp index 3b0dea259..c06bbe325 100644 --- a/examples/light_switch/main/app_driver.cpp +++ b/examples/light_switch/main/app_driver.cpp @@ -27,25 +27,34 @@ using namespace esp_matter::cluster; static const char *TAG = "app_driver"; extern uint16_t switch_endpoint_id; -static uint32_t g_cluster_id = kInvalidClusterId; -static uint32_t g_command_id = kInvalidCommandId; static esp_err_t app_driver_bound_console_handler(int argc, char **argv) { if (argc == 1 && strncmp(argv[0], "help", sizeof("help")) == 0) { printf("Bound commands:\n" "\thelp: Print help\n" - "\tinvoke: . " - "Example: matter esp bound invoke 0x0001 0x0006 0x0002.\n"); + "\tinvoke: . " + "Example: matter esp bound invoke 0x0001 0x0006 0x0002.\n" + "\tinvoke-group: . " + "Example: matter esp bound invoke-group 0x0001 0x0006 0x0002.\n"); } else if (argc == 4 && strncmp(argv[0], "invoke", sizeof("invoke")) == 0) { - uint16_t endpoint_id = strtol((const char *)&argv[1][2], NULL, 16); - uint32_t cluster_id = strtol((const char *)&argv[2][2], NULL, 16); - uint32_t command_id = strtol((const char *)&argv[3][2], NULL, 16); + client::command_handle_t cmd_handle; + uint16_t local_endpoint_id = strtol((const char *)&argv[1][2], NULL, 16); + cmd_handle.cluster_id = strtol((const char *)&argv[2][2], NULL, 16); + cmd_handle.command_id = strtol((const char *)&argv[3][2], NULL, 16); + cmd_handle.is_group = false; - g_cluster_id = cluster_id; - g_command_id = command_id; - client::cluster_update(endpoint_id, cluster_id); - } else { + client::cluster_update(local_endpoint_id, &cmd_handle); + } else if (argc == 4 && strncmp(argv[0], "invoke-group", sizeof("invoke-group")) == 0) { + client::command_handle_t cmd_handle; + uint16_t local_endpoint_id = strtol((const char *)&argv[1][2], NULL, 16); + cmd_handle.cluster_id = strtol((const char *)&argv[2][2], NULL, 16); + cmd_handle.command_id = strtol((const char *)&argv[3][2], NULL, 16); + cmd_handle.is_group = true; + + client::cluster_update(local_endpoint_id, &cmd_handle); + } + else { ESP_LOGE(TAG, "Incorrect arguments. Check help for more details."); return ESP_ERR_INVALID_ARG; } @@ -60,15 +69,14 @@ static esp_err_t app_driver_client_console_handler(int argc, char **argv) "\tinvoke: . " "Example: matter esp client invoke 0x0001 0xBC5C01 0x0001 0x0006 0x0002.\n"); } else if (argc == 6 && strncmp(argv[0], "invoke", sizeof("invoke")) == 0) { + client::command_handle_t cmd_handle; uint8_t fabric_index = strtol((const char *)&argv[1][2], NULL, 16); uint64_t node_id = strtol((const char *)&argv[2][2], NULL, 16); - uint16_t remote_endpoint_id = strtol((const char *)&argv[3][2], NULL, 16); - uint32_t cluster_id = strtol((const char *)&argv[4][2], NULL, 16); - uint32_t command_id = strtol((const char *)&argv[5][2], NULL, 16); + cmd_handle.endpoint_id = strtol((const char *)&argv[3][2], NULL, 16); + cmd_handle.cluster_id = strtol((const char *)&argv[4][2], NULL, 16); + cmd_handle.command_id = strtol((const char *)&argv[5][2], NULL, 16); - g_cluster_id = cluster_id; - g_command_id = command_id; - client::connect(fabric_index, node_id, remote_endpoint_id); + client::connect(fabric_index, node_id, &cmd_handle); } else { ESP_LOGE(TAG, "Incorrect arguments. Check help for more details."); return ESP_ERR_INVALID_ARG; @@ -100,32 +108,43 @@ static void app_driver_register_commands() } void app_driver_client_command_callback(client::peer_device_t *peer_device, uint16_t remote_endpoint_id, - void *priv_data) + client::command_handle_t *cmd_handle, void *priv_data) { - /** TODO: Find a better way to get the cluster_id and command_id. - Once done, move the console commands to esp_matter_client. */ - if (g_cluster_id == OnOff::Id) { - if (g_command_id == OnOff::Commands::Off::Id) { + if (cmd_handle->cluster_id == OnOff::Id) { + if (cmd_handle->command_id == OnOff::Commands::Off::Id) { on_off::command::send_off(peer_device, remote_endpoint_id); - } else if (g_command_id == OnOff::Commands::On::Id) { + } else if (cmd_handle->command_id == OnOff::Commands::On::Id) { on_off::command::send_on(peer_device, remote_endpoint_id); - } else if (g_command_id == OnOff::Commands::Toggle::Id) { + } else if (cmd_handle->command_id == OnOff::Commands::Toggle::Id) { on_off::command::send_toggle(peer_device, remote_endpoint_id); } } } +void app_driver_client_group_command_callback(uint8_t fabric_index, uint16_t group_id, + client::command_handle_t *cmd_handle, void *priv_data) +{ + if (cmd_handle->cluster_id == OnOff::Id) { + if (cmd_handle->command_id == OnOff::Commands::Off::Id) { + on_off::command::group_send_off(fabric_index, group_id); + } else if (cmd_handle->command_id == OnOff::Commands::On::Id) { + on_off::command::group_send_on(fabric_index, group_id); + } else if (cmd_handle->command_id == OnOff::Commands::Toggle::Id) { + on_off::command::group_send_toggle(fabric_index, group_id); + } + } +} + static void app_driver_button_toggle_cb(void *arg) { ESP_LOGI(TAG, "Toggle button pressed"); - uint16_t endpoint_id = switch_endpoint_id; - uint32_t cluster_id = OnOff::Id; - uint32_t command_id = OnOff::Commands::Toggle::Id; + client::command_handle_t cmd_handle; + cmd_handle.cluster_id = OnOff::Id; + cmd_handle.command_id = OnOff::Commands::Toggle::Id; + cmd_handle.is_group = false; - g_cluster_id = cluster_id; - g_command_id = command_id; lock::chip_stack_lock(portMAX_DELAY); - client::cluster_update(endpoint_id, cluster_id); + client::cluster_update(switch_endpoint_id, &cmd_handle); lock::chip_stack_unlock(); } @@ -145,7 +164,7 @@ app_driver_handle_t app_driver_switch_init() /* Other initializations */ app_driver_register_commands(); - client::set_command_callback(app_driver_client_command_callback, NULL); + client::set_command_callback(app_driver_client_command_callback, app_driver_client_group_command_callback, NULL); return (app_driver_handle_t)handle; } diff --git a/examples/light_switch/main/app_main.cpp b/examples/light_switch/main/app_main.cpp index 010c336ae..530a42f8a 100644 --- a/examples/light_switch/main/app_main.cpp +++ b/examples/light_switch/main/app_main.cpp @@ -109,6 +109,10 @@ extern "C" void app_main() ESP_LOGE(TAG, "Matter node creation failed"); } + /* Add group cluster to the switch endpoint */ + cluster::groups::config_t groups_config; + cluster::groups::create(endpoint, &groups_config, CLUSTER_FLAG_SERVER | CLUSTER_FLAG_CLIENT); + switch_endpoint_id = endpoint::get_id(endpoint); ESP_LOGI(TAG, "Switch created with endpoint_id %d", switch_endpoint_id);