Merge branch 'group-binding' into 'main'

Example: Add group binding for the light-switch app

See merge request app-frameworks/esp-matter!188
This commit is contained in:
Shu Chen
2022-09-27 10:21:00 +08:00
6 changed files with 236 additions and 54 deletions
+88 -12
View File
@@ -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<command_handle_t *>(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<command_handle_t *>(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<chip::OnDeviceConnected> success_callback(esp_matter_connection_success_callback, NULL);
static Callback<chip::OnDeviceConnectionFailure> failure_callback(esp_matter_connection_failure_callback, NULL);
command_handle_t *context = chip::Platform::New<command_handle_t>(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<void *>(context);
failure_callback.mContext = static_cast<void *>(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<command_handle_t *>(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<command_handle_t *>(context));
}
}
esp_err_t cluster_update(uint16_t local_endpoint_id, command_handle_t *cmd_handle)
{
command_handle_t *context = chip::Platform::New<command_handle_t>(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<void *>(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 */
@@ -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 */
+41 -9
View File
@@ -705,6 +705,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;
@@ -715,10 +731,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
*
@@ -740,39 +770,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 */
+50 -2
View File
@@ -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 <endpoint_id> <cluster_id> <command_id>
matter esp bound invoke <local_endpoint_id> <cluster_id> <command_id>
```
- 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 <local_endpoint_id> <cluster_id> <command_id>
```
- 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
+50 -31
View File
@@ -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: <endpoint_id> <cluster_id> <command_id>. "
"Example: matter esp bound invoke 0x0001 0x0006 0x0002.\n");
"\tinvoke: <local_endpoint_id> <cluster_id> <command_id>. "
"Example: matter esp bound invoke 0x0001 0x0006 0x0002.\n"
"\tinvoke-group: <local_endpoint_id> <cluster_id> <command_id>. "
"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: <fabric_index> <remote_node_id> <remote_endpoint_id> <cluster_id> <command_id>. "
"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;
}
+4
View File
@@ -104,6 +104,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);