Merge branch 'controller/commissioning_window_opener' into 'main'

controller: Add support for timed-invoke command and add open enhanced commissioning window command

See merge request app-frameworks/esp-matter!766
This commit is contained in:
Shu Chen
2024-06-11 12:04:56 +08:00
5 changed files with 419 additions and 94 deletions
@@ -23,6 +23,7 @@
#include <app/server/Server.h>
#include <crypto/CHIPCryptoPAL.h>
#include <lib/core/Optional.h>
#include <setup_payload/ManualSetupPayloadGenerator.h>
#include <setup_payload/QRCodeSetupPayloadGenerator.h>
#include <setup_payload/SetupPayload.h>
@@ -154,8 +155,8 @@ void cluster_command::on_device_connected_fcn(void *context, ExchangeManager &ex
chip::OperationalDeviceProxy device_proxy(&exchangeMgr, sessionHandle);
chip::app::CommandPathParams command_path = {cmd->m_endpoint_id, 0, cmd->m_cluster_id, cmd->m_command_id,
chip::app::CommandPathFlags::kEndpointIdValid};
interaction::invoke::send_request(context, &device_proxy, command_path, cmd->m_command_data_field, cmd->on_success_cb,
cmd->on_error_cb, chip::NullOptional);
interaction::invoke::send_request(context, &device_proxy, command_path, cmd->m_command_data_field,
cmd->on_success_cb, cmd->on_error_cb, cmd->m_timed_invoke_timeout_ms);
chip::Platform::Delete(cmd);
return;
}
@@ -255,14 +256,15 @@ esp_err_t cluster_command::send_command()
}
esp_err_t send_invoke_cluster_command(uint64_t destination_id, uint16_t endpoint_id, uint32_t cluster_id,
uint32_t command_id, const char *command_data_field)
uint32_t command_id, const char *command_data_field,
chip::Optional<uint16_t> timed_invoke_timeout_ms)
{
if (command_data_field && strlen(command_data_field) >= k_command_data_field_buffer_size) {
ESP_LOGE(TAG, "The command data field buffer is too small for this command, please increase the buffer size");
return ESP_ERR_INVALID_ARG;
}
cluster_command *cmd =
chip::Platform::New<cluster_command>(destination_id, endpoint_id, cluster_id, command_id, command_data_field);
cluster_command *cmd = chip::Platform::New<cluster_command>(destination_id, endpoint_id, cluster_id, command_id,
command_data_field, timed_invoke_timeout_ms);
if (!cmd) {
ESP_LOGE(TAG, "Failed to alloc memory for cluster_command");
return ESP_ERR_NO_MEM;
@@ -18,6 +18,7 @@
#include <esp_matter.h>
#include <esp_matter_client.h>
#include <esp_matter_mem.h>
#include <lib/core/Optional.h>
namespace esp_matter {
namespace controller {
@@ -39,12 +40,14 @@ class cluster_command {
public:
cluster_command(uint64_t destination_id, uint16_t endpoint_id, uint32_t cluster_id, uint32_t command_id,
const char *command_data_field,
const chip::Optional<uint16_t> timed_invoke_timeout_ms = chip::NullOptional,
custom_command_callback::on_success_callback_t on_success = default_success_fcn,
custom_command_callback::on_error_callback_t on_error = default_error_fcn)
: m_destination_id(destination_id)
, m_endpoint_id(endpoint_id)
, m_cluster_id(cluster_id)
, m_command_id(command_id)
, m_timed_invoke_timeout_ms(timed_invoke_timeout_ms)
, on_device_connected_cb(on_device_connected_fcn, this)
, on_device_connection_failure_cb(on_device_connection_failure_fcn, this)
, on_success_cb(on_success)
@@ -71,6 +74,7 @@ private:
uint32_t m_cluster_id;
uint32_t m_command_id;
char m_command_data_field[k_command_data_field_buffer_size];
chip::Optional<uint16_t> m_timed_invoke_timeout_ms;
static void on_device_connected_fcn(void *context, ExchangeManager &exchangeMgr,
const SessionHandle &sessionHandle);
@@ -101,12 +105,14 @@ private:
* @param[in] command_id CommandId
* @param[in] command_data_field Command data string with JSON format
* (https://docs.espressif.com/projects/esp-matter/en/latest/esp32/developing.html#cluster-commands)
* @param[in] timed_invoke_timeout_ms Timeout in millisecond for timed-invoke command
*
* @return ESP_OK on success.
* @return error in case of failure.
*/
esp_err_t send_invoke_cluster_command(uint64_t destination_id, uint16_t endpoint_id, uint32_t cluster_id,
uint32_t command_id, const char *command_data_field);
uint32_t command_id, const char *command_data_field,
chip::Optional<uint16_t> timed_invoke_timeout_ms = chip::NullOptional);
} // namespace controller
} // namespace esp_matter
@@ -0,0 +1,185 @@
// Copyright 2024 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 <esp_err.h>
#include <esp_log.h>
#include <esp_matter_controller_client.h>
#include <esp_matter_controller_utils.h>
#include <esp_matter_controller_commissioning_window_opener.h>
#include <app-common/zap-generated/cluster-objects.h>
#include <app/server/Server.h>
#include <controller/CHIPCluster.h>
#include <crypto/CHIPCryptoPAL.h>
#include <lib/core/CHIPError.h>
#include <lib/core/NodeId.h>
#include <lib/core/Optional.h>
#include <setup_payload/ManualSetupPayloadGenerator.h>
#include <setup_payload/QRCodeSetupPayloadGenerator.h>
using namespace chip::app::Clusters;
#define TAG "controller"
namespace esp_matter {
namespace controller {
esp_err_t commissioning_window_opener::send_open_commissioning_window_command(uint64_t node_id, bool is_enhanced,
uint16_t timeout, uint32_t iteration,
uint16_t discriminator,
uint16_t timed_invoke_timeout_ms)
{
if (!chip::IsOperationalNodeId(node_id)) {
return ESP_ERR_INVALID_ARG;
}
m_is_enhanced = is_enhanced;
m_timout = timeout;
m_iteration = iteration;
m_discriminator = discriminator;
m_timed_invoke_timeout_ms = timed_invoke_timeout_ms;
#ifdef CONFIG_ESP_MATTER_ENABLE_MATTER_SERVER
chip::Server &server = chip::Server::GetInstance();
server.GetCASESessionManager()->FindOrEstablishSession(ScopedNodeId(node_id, get_fabric_index()),
&on_device_connected_cb, &on_device_connection_failure_cb);
return ESP_OK;
#else
auto &controller_instance = esp_matter::controller::matter_controller_client::get_instance();
#ifdef CONFIG_ESP_MATTER_COMMISSIONER_ENABLE
if (CHIP_NO_ERROR ==
controller_instance.get_commissioner()->GetConnectedDevice(node_id, &on_device_connected_cb,
&on_device_connection_failure_cb)) {
return ESP_OK;
}
#else
if (CHIP_NO_ERROR ==
controller_instance.get_controller()->GetConnectedDevice(node_id, &on_device_connected_cb,
&on_device_connection_failure_cb)) {
return ESP_OK;
}
#endif // CONFIG_ESP_MATTER_COMMISSIONER_ENABLE
#endif // CONFIG_ESP_MATTER_ENABLE_MATTER_SERVER
return ESP_OK;
}
static esp_err_t generate_pase_verifier(uint32_t iteration, uint32_t &pincode, chip::MutableByteSpan &salt,
chip::Crypto::Spake2pVerifier &verifier)
{
if (chip::Crypto::DRBG_get_bytes(salt.data(), salt.size()) != CHIP_NO_ERROR) {
ESP_LOGE(TAG, "Failed to generate salt");
return ESP_FAIL;
}
if (chip::PASESession::GeneratePASEVerifier(verifier, iteration, salt, true, pincode) != CHIP_NO_ERROR) {
ESP_LOGE(TAG, "Failed to generate PASE verifier");
return ESP_FAIL;
}
return ESP_OK;
}
static esp_err_t generate_manual_code(uint32_t pincode, uint16_t discriminator, char *manual_code_buffer,
size_t buffer_size)
{
chip::SetupPayload payload = chip::SetupPayload();
payload.setUpPINCode = pincode;
payload.version = 0;
payload.discriminator.SetLongValue(discriminator);
payload.rendezvousInformation.SetValue(chip::RendezvousInformationFlag::kOnNetwork);
char payload_buffer[chip::QRCodeBasicSetupPayloadGenerator::kMaxQRCodeBase38RepresentationLength + 1];
chip::MutableCharSpan manual_code(payload_buffer);
CHIP_ERROR err = chip::ManualSetupPayloadGenerator(payload).payloadDecimalStringRepresentation(manual_code);
if (err != CHIP_NO_ERROR || manual_code.size() >= buffer_size) {
return ESP_FAIL;
}
strncpy(manual_code_buffer, manual_code.data(), manual_code.size());
manual_code_buffer[manual_code.size()] = 0;
return ESP_OK;
}
void commissioning_window_opener::on_device_connected_fcn(void *context, ExchangeManager &exchangeMgr,
const SessionHandle &sessionHandle)
{
commissioning_window_opener *window_opener = reinterpret_cast<commissioning_window_opener *>(context);
if (!window_opener) {
return;
}
if (window_opener->m_is_enhanced) {
uint8_t salt_buffer[chip::Crypto::kSpake2p_Max_PBKDF_Salt_Length];
chip::MutableByteSpan salt = chip::MutableByteSpan(salt_buffer);
chip::Crypto::Spake2pVerifier verifier;
if (generate_pase_verifier(window_opener->m_iteration, window_opener->m_pincode, salt, verifier) != ESP_OK) {
return;
}
chip::Spake2pVerifierSerialized serialized_verifier;
chip::MutableByteSpan serialized_verifier_span(serialized_verifier);
if (verifier.Serialize(serialized_verifier_span) != CHIP_NO_ERROR) {
ESP_LOGE(TAG, "Failed to serialize the verifier");
return;
}
AdministratorCommissioning::Commands::OpenCommissioningWindow::Type command_data;
command_data.commissioningTimeout = window_opener->m_timout;
command_data.PAKEPasscodeVerifier = serialized_verifier_span;
command_data.discriminator = window_opener->m_discriminator;
command_data.iterations = window_opener->m_iteration;
command_data.salt = salt;
chip::Controller::ClusterBase cluster(exchangeMgr, sessionHandle, window_opener->m_default_remote_endpoint_id);
cluster.InvokeCommand(command_data, window_opener, send_command_success_callback, send_command_failure_callback,
chip::MakeOptional(window_opener->m_timed_invoke_timeout_ms));
} else {
AdministratorCommissioning::Commands::OpenBasicCommissioningWindow::Type command_data;
command_data.commissioningTimeout = window_opener->m_timout;
chip::Controller::ClusterBase cluster(exchangeMgr, sessionHandle, window_opener->m_default_remote_endpoint_id);
cluster.InvokeCommand(command_data, window_opener, send_command_success_callback, send_command_failure_callback,
chip::MakeOptional(window_opener->m_timed_invoke_timeout_ms));
}
}
void commissioning_window_opener::on_device_connection_failure_fcn(void *context, const ScopedNodeId &peerId,
CHIP_ERROR error)
{
commissioning_window_opener *window_opener = reinterpret_cast<commissioning_window_opener *>(context);
if (window_opener) {
ESP_LOGE(TAG, "Failed to establish CASE session for open %s commisioning window command",
window_opener->m_is_enhanced ? "enhanced" : "basic");
}
}
void commissioning_window_opener::send_command_success_callback(void *context,
const chip::app::DataModel::NullObjectType &data)
{
commissioning_window_opener *window_opener = reinterpret_cast<commissioning_window_opener *>(context);
if (!window_opener) {
return;
}
ESP_LOGI(TAG, "Open %s commissioning window finished", window_opener->m_is_enhanced ? "enhanced" : "basic");
if (window_opener->m_is_enhanced) {
if (window_opener->m_callback) {
char manual_code[22];
if (generate_manual_code(window_opener->m_pincode, window_opener->m_discriminator, manual_code,
sizeof(manual_code)) == ESP_OK) {
window_opener->m_callback(manual_code);
}
}
}
}
void commissioning_window_opener::send_command_failure_callback(void *context, CHIP_ERROR error)
{
commissioning_window_opener *window_opener = reinterpret_cast<commissioning_window_opener *>(context);
if (window_opener) {
ESP_LOGE(TAG, "Failed to send open %s commisioning window command",
window_opener->m_is_enhanced ? "enhanced" : "basic");
}
}
} // namespace controller
} // namespace esp_matter
@@ -0,0 +1,78 @@
// Copyright 2024 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 <esp_matter.h>
#include <esp_matter_client.h>
#include <esp_matter_mem.h>
#include <lib/core/ScopedNodeId.h>
#include <messaging/ExchangeMgr.h>
#include <transport/Session.h>
using chip::ScopedNodeId;
using chip::SessionHandle;
using chip::Messaging::ExchangeManager;
namespace esp_matter {
namespace controller {
class commissioning_window_opener {
public:
typedef void (*commissioning_window_open_callback_t)(const char *manual_code);
static commissioning_window_opener &get_instance()
{
static commissioning_window_opener instance;
return instance;
}
void set_callback(commissioning_window_open_callback_t callback) { m_callback = callback; }
esp_err_t send_open_commissioning_window_command(uint64_t node_id, bool is_enhanced, uint16_t timeout,
uint32_t iteration, uint16_t discriminator,
uint16_t timed_invoke_timeout_ms);
uint16_t m_default_remote_endpoint_id = 0;
private:
commissioning_window_opener()
: on_device_connected_cb(on_device_connected_fcn, this)
, on_device_connection_failure_cb(on_device_connection_failure_fcn, this)
{
}
~commissioning_window_opener() {}
static void on_device_connected_fcn(void *context, ExchangeManager &exchangeMgr,
const SessionHandle &sessionHandle);
static void on_device_connection_failure_fcn(void *context, const ScopedNodeId &peerId, CHIP_ERROR error);
static void send_command_success_callback(void *context, const chip::app::DataModel::NullObjectType &data);
static void send_command_failure_callback(void *context, CHIP_ERROR error);
chip::Callback::Callback<chip::OnDeviceConnected> on_device_connected_cb;
chip::Callback::Callback<chip::OnDeviceConnectionFailure> on_device_connection_failure_cb;
uint32_t m_discriminator = 0;
bool m_is_enhanced = false;
uint32_t m_pincode = 0;
uint16_t m_timout = 0;
uint32_t m_iteration = 0;
uint16_t m_timed_invoke_timeout_ms = 0;
commissioning_window_open_callback_t m_callback = nullptr;
};
} // namespace controller
} // namespace esp_matter
@@ -16,8 +16,9 @@
*/
#include <esp_check.h>
#include <esp_matter_controller_cluster_command.h>
#include <esp_matter_controller_client.h>
#include <esp_matter_controller_cluster_command.h>
#include <esp_matter_controller_commissioning_window_opener.h>
#include <esp_matter_controller_console.h>
#include <esp_matter_controller_group_settings.h>
#include <esp_matter_controller_pairing_command.h>
@@ -238,13 +239,17 @@ static esp_err_t controller_udc_handler(int argc, char **argv)
return ESP_ERR_INVALID_ARG;
}
controller::matter_controller_client::get_instance()
.get_commissioner()->GetUserDirectedCommissioningServer()->ResetUDCClientProcessingStates();
.get_commissioner()
->GetUserDirectedCommissioningServer()
->ResetUDCClientProcessingStates();
} else if (strncmp(argv[0], "print", sizeof("print")) == 0) {
if (argc != 1) {
return ESP_ERR_INVALID_ARG;
}
controller::matter_controller_client::get_instance()
.get_commissioner()->GetUserDirectedCommissioningServer()->PrintUDCClients();
.get_commissioner()
->GetUserDirectedCommissioningServer()
->PrintUDCClients();
} else if (strncmp(argv[0], "commission", sizeof("commission")) == 0) {
if (argc != 3) {
return ESP_ERR_INVALID_ARG;
@@ -260,7 +265,9 @@ static esp_err_t controller_udc_handler(int argc, char **argv)
chip::NodeId gRemoteId = chip::kTestDeviceNodeId;
chip::RendezvousParameters params = chip::RendezvousParameters()
.SetSetupPINCode(pincode).SetDiscriminator(state->GetLongDiscriminator()).SetPeerAddress(state->GetPeerAddress());
.SetSetupPINCode(pincode)
.SetDiscriminator(state->GetLongDiscriminator())
.SetPeerAddress(state->GetPeerAddress());
do {
chip::DRBG_get_bytes(reinterpret_cast<uint8_t *>(&gRemoteId), sizeof(gRemoteId));
} while (!chip::IsOperationalNodeId(gRemoteId));
@@ -342,6 +349,30 @@ static esp_err_t controller_group_settings_handler(int argc, char **argv)
}
#endif
static void print_manual_code(const char *manual_code)
{
ESP_LOGI(TAG,
"*************************************Manual Code: [%s]**********************************************",
manual_code);
}
static esp_err_t open_commissioning_window_handler(int argc, char **argv)
{
if (argc != 5) {
return ESP_ERR_INVALID_ARG;
}
uint64_t node_id = string_to_uint64(argv[0]);
uint8_t option = string_to_uint8(argv[1]);
bool is_enhanced = option == 1;
uint16_t window_timeout = string_to_uint16(argv[2]);
uint32_t iteration = string_to_uint32(argv[3]);
uint16_t discriminator = string_to_uint16(argv[4]);
controller::commissioning_window_opener::get_instance().set_callback(print_manual_code);
return controller::commissioning_window_opener::get_instance().send_open_commissioning_window_command(
node_id, is_enhanced, window_timeout, iteration, discriminator, 10000 /* timed_invoke_timeout_ms */);
}
static esp_err_t controller_invoke_command_handler(int argc, char **argv)
{
if (argc < 4) {
@@ -353,6 +384,14 @@ static esp_err_t controller_invoke_command_handler(int argc, char **argv)
uint32_t cluster_id = string_to_uint32(argv[2]);
uint32_t command_id = string_to_uint32(argv[3]);
if (argc > 5) {
uint16_t timed_invoke_timeout_ms = string_to_uint16(argv[5]);
if (timed_invoke_timeout_ms > 0) {
return controller::send_invoke_cluster_command(node_id, endpoint_id, cluster_id, command_id, argv[4],
chip::MakeOptional(timed_invoke_timeout_ms));
}
}
return controller::send_invoke_cluster_command(node_id, endpoint_id, cluster_id, command_id,
argc > 4 ? argv[4] : NULL);
}
@@ -466,98 +505,113 @@ static esp_err_t controller_dispatch(int argc, char **argv)
esp_err_t controller_register_commands()
{
// Subcommands for root command: `controller <subcommand>`
const static command_t controller_sub_commands[] =
{ {
.name = "help",
.description = "print this page",
.handler = controller_help_handler,
},
const static command_t controller_sub_commands[] = {
{
.name = "help",
.description = "print this page",
.handler = controller_help_handler,
},
#if CONFIG_ESP_MATTER_COMMISSIONER_ENABLE
{
.name = "pairing",
.description = "Pairing a node.\n"
"\tUsage: controller pairing onnetwork [nodeid] [pincode] OR\n"
"\tcontroller pairing ble-wifi [nodeid] [ssid] [password] [pincode] [discriminator] OR\n"
"\tcontroller pairing ble-thread [nodeid] [dataset] [pincode] [discriminator]",
.handler = controller_pairing_handler,
},
{
.name = "group-settings",
.description = "Managing the groups and keysets of the controller.\n"
"\tUsage: controller group-settings <sub-commands>",
.handler = controller_group_settings_handler,
},
{
.name = "pairing",
.description = "Pairing a node.\n"
"\tUsage: controller pairing onnetwork <nodeid> <pincode> OR\n"
"\tcontroller pairing ble-wifi <nodeid> <ssid> <password> <pincode> <discriminator> OR\n"
"\tcontroller pairing ble-thread <nodeid> <dataset> <pincode> <discriminator>",
.handler = controller_pairing_handler,
},
{
.name = "group-settings",
.description = "Managing the groups and keysets of the controller.\n"
"\tUsage: controller group-settings <sub-commands>",
.handler = controller_group_settings_handler,
},
#if CHIP_DEVICE_CONFIG_ENABLE_COMMISSIONER_DISCOVERY
{
.name = "udc",
.description = "UDC command.\n"
"\tUsage: controller udc reset OR\n"
"\tcontroller udc print OR\n"
"\tcontroller udc commission [pincode] [udc-entry]",
.handler = controller_udc_handler,
},
{
.name = "udc",
.description = "UDC command.\n"
"\tUsage: controller udc reset OR\n"
"\tcontroller udc print OR\n"
"\tcontroller udc commission <pincode> <udc-entry>",
.handler = controller_udc_handler,
},
#endif
#endif // CONFIG_ESP_MATTER_COMMISSIONER_ENABLE
{
.name = "invoke-cmd",
.description =
"Send command to the nodes.\n"
"\tUsage: controller invoke-cmd [node-id|group-id] [endpoint-id] [cluster-id] [command-id] [payload]\n"
"\tNotes: group-id should start with prefix '0xFFFFFFFFFFFF', endpoint-id will be ignored if the fist "
"parameter is group-id.\n"
"\tNotes: The payload should be a JSON object that includes all the command data fields defined in the "
"SPEC. You can get the format of the payload from "
"https://docs.espressif.com/projects/esp-matter/en/latest/esp32/developing.html#cluster-commands",
.handler = controller_invoke_command_handler,
},
{
.name = "read-attr",
.description = "Read attributes of the nodes.\n"
"\tUsage: controller read-attr [node-id] [endpoint-id] [cluster-id] [attr-id]",
.handler = controller_read_attr_handler,
},
{
.name = "write-attr",
.description =
"Write attributes of the nodes.\n"
"\tUsage: controller write-attr [node-id|group-id] [endpoint-id] [cluster-id] [attr-id] "
"[attr-value]\n"
"\tNotes: attr-value should be a JSON object that contains the attribute value JSON item."
"You can get the format of the attr-value from "
"https://docs.espressif.com/projects/esp-matter/en/latest/esp32/developing.html#write-attribute-commands",
.handler = controller_write_attr_handler,
},
{
.name = "read-event",
.description = "Read events of the nodes.\n"
"\tUsage: controller read-event [node-id] [endpoint-id] [cluster-id] [event-id]",
.handler = controller_read_event_handler,
},
{
.name = "subs-attr",
.description = "Subscribe attributes of the nodes.\n"
"\tUsage: controller subs-attr [node-id] [endpoint-id] [cluster-id] [attr-id] "
"[min-interval] [max-interval]",
.handler = controller_subscribe_attr_handler,
},
{
.name = "subs-event",
.description = "Subscribe events of the nodes.\n"
"\tUsage: controller subs-event [node-id] [endpoint-id] [cluster-id] [event-id] "
"[min-interval] [max-interval]",
.handler = controller_subscribe_event_handler,
},
{
.name = "shutdown-subs",
.description = "Shutdown subscription.\n"
"\tUsage: controller shutdown-subs [node-id] [subscription-id]",
.handler = controller_shutdown_subscription_handler,
},
{
.name = "open-commissioning-window",
.description =
"Send command to open basic/enhanced commissioning window\n"
"\tUsage: controller open-commissioning-window <node-id> <option> <window-timeout> <iteration> "
"<discriminator>\n"
"\toption: 1 to use enhanced commissioning window. 0 to use basic commissioning window.\n"
"\titeration: Number of PBKDF iterations to use to derive the verifier. Ignored if 'option' is 0.\n"
"\tdiscriminator: Discriminator to use for advertising. Ignored if 'option' is 0.",
.handler = open_commissioning_window_handler,
},
{
.name = "invoke-cmd",
.description =
"Send command to the nodes.\n"
"\tUsage: controller invoke-cmd <node-id|group-id> <endpoint-id> <cluster-id> <command-id> "
"[command_data] [timed_invoke_timeout_ms]\n"
"\tNotes: group-id should start with prefix '0xFFFFFFFFFFFF', endpoint-id will be ignored if the fist "
"parameter is group-id.\n"
"\tNotes: The command_data should be a JSON object that includes all the command data fields defined "
"in the "
"SPEC. You can get the format of the payload from "
"https://docs.espressif.com/projects/esp-matter/en/latest/esp32/developing.html#cluster-commands\n"
"\tNotes: The timed_invoke_timeout_ms should be used with command_data. If the command has no command "
"data field, please use '\"{}\"' as the command_data ",
.handler = controller_invoke_command_handler,
},
{
.name = "read-attr",
.description = "Read attributes of the nodes.\n"
"\tUsage: controller read-attr <node-id> <endpoint-id> <cluster-id> <attr-id>",
.handler = controller_read_attr_handler,
},
{
.name = "write-attr",
.description =
"Write attributes of the nodes.\n"
"\tUsage: controller write-attr <node-id> <endpoint-id> <cluster-id> <attr-id> <attr-value>\n"
"\tNotes: attr-value should be a JSON object that contains the attribute value JSON item."
"You can get the format of the attr-value from "
"https://docs.espressif.com/projects/esp-matter/en/latest/esp32/"
"developing.html#write-attribute-commands",
.handler = controller_write_attr_handler,
},
{
.name = "read-event",
.description = "Read events of the nodes.\n"
"\tUsage: controller read-event <node-id> <endpoint-id> <cluster-id> <event-id>",
.handler = controller_read_event_handler,
},
{
.name = "subs-attr",
.description = "Subscribe attributes of the nodes.\n"
"\tUsage: controller subs-attr <node-id> <endpoint-id> <cluster-id> <attr-id> "
"<min-interval> <max-interval>",
.handler = controller_subscribe_attr_handler,
},
{
.name = "subs-event",
.description = "Subscribe events of the nodes.\n"
"\tUsage: controller subs-attr <node-id> <endpoint-id> <cluster-id> <event-id> "
"<min-interval> <max-interval>",
.handler = controller_subscribe_event_handler,
},
{
.name = "shutdown-subs",
.description = "Shutdown subscription.\n"
"\tUsage: controller shutdown-subs <node-id> <subscription-id>",
.handler = controller_shutdown_subscription_handler,
},
};
const static command_t controller_command = {
.name = "controller",
.description = "Controller commands. Usage: matter esp controller [command_name]",
.description = "Controller commands. Usage: matter esp controller <command_name>",
.handler = controller_dispatch,
};
// Register the controller commands