diff --git a/components/esp_matter_controller/commands/esp_matter_controller_cluster_command.cpp b/components/esp_matter_controller/commands/esp_matter_controller_cluster_command.cpp index d0ede9310..65219653a 100644 --- a/components/esp_matter_controller/commands/esp_matter_controller_cluster_command.cpp +++ b/components/esp_matter_controller/commands/esp_matter_controller_cluster_command.cpp @@ -23,6 +23,7 @@ #include #include +#include #include #include #include @@ -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 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(destination_id, endpoint_id, cluster_id, command_id, command_data_field); + cluster_command *cmd = chip::Platform::New(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; diff --git a/components/esp_matter_controller/commands/esp_matter_controller_cluster_command.h b/components/esp_matter_controller/commands/esp_matter_controller_cluster_command.h index 2004cc6cf..700932b23 100644 --- a/components/esp_matter_controller/commands/esp_matter_controller_cluster_command.h +++ b/components/esp_matter_controller/commands/esp_matter_controller_cluster_command.h @@ -18,6 +18,7 @@ #include #include #include +#include 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 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 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 timed_invoke_timeout_ms = chip::NullOptional); } // namespace controller } // namespace esp_matter diff --git a/components/esp_matter_controller/commands/esp_matter_controller_commissioning_window_opener.cpp b/components/esp_matter_controller/commands/esp_matter_controller_commissioning_window_opener.cpp new file mode 100644 index 000000000..a9680fae2 --- /dev/null +++ b/components/esp_matter_controller/commands/esp_matter_controller_commissioning_window_opener.cpp @@ -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 +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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(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(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(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(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 diff --git a/components/esp_matter_controller/commands/esp_matter_controller_commissioning_window_opener.h b/components/esp_matter_controller/commands/esp_matter_controller_commissioning_window_opener.h new file mode 100644 index 000000000..dbe7af9dd --- /dev/null +++ b/components/esp_matter_controller/commands/esp_matter_controller_commissioning_window_opener.h @@ -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 +#include +#include +#include +#include +#include + +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 on_device_connected_cb; + chip::Callback::Callback 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 diff --git a/components/esp_matter_controller/core/esp_matter_controller_console.cpp b/components/esp_matter_controller/core/esp_matter_controller_console.cpp index bf5fb9783..c7c65f859 100644 --- a/components/esp_matter_controller/core/esp_matter_controller_console.cpp +++ b/components/esp_matter_controller/core/esp_matter_controller_console.cpp @@ -16,8 +16,9 @@ */ #include -#include #include +#include +#include #include #include #include @@ -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(&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 ` - 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 ", - .handler = controller_group_settings_handler, - }, + { + .name = "pairing", + .description = "Pairing a node.\n" + "\tUsage: controller pairing onnetwork OR\n" + "\tcontroller pairing ble-wifi OR\n" + "\tcontroller pairing ble-thread ", + .handler = controller_pairing_handler, + }, + { + .name = "group-settings", + .description = "Managing the groups and keysets of the controller.\n" + "\tUsage: controller group-settings ", + .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 ", + .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