From 5389422cd177ba161dc7f201627a78e31c6feff6 Mon Sep 17 00:00:00 2001 From: chendejin Date: Fri, 26 Jul 2024 12:26:12 +0800 Subject: [PATCH] components/esp_matter_bridge: add or remove bridged device in console --- components/esp_matter_bridge/CMakeLists.txt | 2 +- .../esp_matter_bridge/esp_matter_bridge.cpp | 3 + .../esp_matter_console_bridge.cpp | 286 ++++++++++++++++++ .../esp_matter_console_bridge.h | 26 ++ 4 files changed, 316 insertions(+), 1 deletion(-) create mode 100644 components/esp_matter_bridge/esp_matter_console_bridge.cpp create mode 100644 components/esp_matter_bridge/esp_matter_console_bridge.h diff --git a/components/esp_matter_bridge/CMakeLists.txt b/components/esp_matter_bridge/CMakeLists.txt index d2a975173..17fe53c95 100644 --- a/components/esp_matter_bridge/CMakeLists.txt +++ b/components/esp_matter_bridge/CMakeLists.txt @@ -1,3 +1,3 @@ -idf_component_register(SRCS "${CMAKE_CURRENT_LIST_DIR}/esp_matter_bridge.cpp" +idf_component_register(SRC_DIRS "${CMAKE_CURRENT_LIST_DIR}" INCLUDE_DIRS "${CMAKE_CURRENT_LIST_DIR}" REQUIRES esp_matter) diff --git a/components/esp_matter_bridge/esp_matter_bridge.cpp b/components/esp_matter_bridge/esp_matter_bridge.cpp index ea7216464..4531b8b2e 100644 --- a/components/esp_matter_bridge/esp_matter_bridge.cpp +++ b/components/esp_matter_bridge/esp_matter_bridge.cpp @@ -449,6 +449,9 @@ esp_err_t factory_reset() err = nvs_erase_all(handle); nvs_commit(handle); nvs_close(handle); + for (size_t idx = 0; idx < MAX_BRIDGED_DEVICE_COUNT; ++idx) { + bridged_endpoint_id_array[idx] = chip::kInvalidEndpointId; + } return err; } diff --git a/components/esp_matter_bridge/esp_matter_console_bridge.cpp b/components/esp_matter_bridge/esp_matter_console_bridge.cpp new file mode 100644 index 000000000..01a393d8f --- /dev/null +++ b/components/esp_matter_bridge/esp_matter_console_bridge.cpp @@ -0,0 +1,286 @@ +// 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 + +using namespace esp_matter::endpoint; +namespace esp_matter { +namespace console { +static const char *TAG = "esp_matter_console_bridge"; +static engine bridge_console; + +typedef struct cli_bridged_device { + esp_matter_bridge::device_t *device; + struct cli_bridged_device *next; +} cli_bridged_device_t; + +static cli_bridged_device_t *cli_device = NULL; + +template +esp_err_t add_device(esp_matter::endpoint_t *ep) +{ + config _config; + return add(ep, &_config); +} + +struct device_type_handler { + const char *device_type_name; + uint32_t device_type_id; + esp_err_t (*add_fun)(esp_matter::endpoint_t *ep); +}; + +#define ADD_DEVICE_FUN(type) add_device + +// Add device_handlers to support more device types +const device_type_handler device_handlers[] = { + {"on_off_light", ESP_MATTER_ON_OFF_LIGHT_DEVICE_TYPE_ID, ADD_DEVICE_FUN(on_off_light)}, + {"dimmable_light", ESP_MATTER_DIMMABLE_LIGHT_DEVICE_TYPE_ID, ADD_DEVICE_FUN(dimmable_light)}, + {"on_off_switch", ESP_MATTER_ON_OFF_SWITCH_DEVICE_TYPE_ID, ADD_DEVICE_FUN(on_off_switch)}, + {"occupancy_sensor", ESP_MATTER_OCCUPANCY_SENSOR_DEVICE_TYPE_ID, ADD_DEVICE_FUN(occupancy_sensor)}, + {"on_off_plugin_unit", ESP_MATTER_ON_OFF_PLUGIN_UNIT_DEVICE_TYPE_ID, ADD_DEVICE_FUN(on_off_plugin_unit)}, + {"color_temperature_light", ESP_MATTER_COLOR_TEMPERATURE_LIGHT_DEVICE_TYPE_ID, ADD_DEVICE_FUN(color_temperature_light)}, + {"extended_color_light", ESP_MATTER_EXTENDED_COLOR_LIGHT_DEVICE_TYPE_ID, ADD_DEVICE_FUN(extended_color_light)}, + {"thermostat", ESP_MATTER_THERMOSTAT_DEVICE_TYPE_ID, ADD_DEVICE_FUN(thermostat)}, + {"temperature_sensor", ESP_MATTER_TEMPERATURE_SENSOR_DEVICE_TYPE_ID, ADD_DEVICE_FUN(temperature_sensor)}, +}; + +static uint32_t string_to_uint32(char *str) +{ + if (strlen(str) > 2 && str[0] == '0' && str[1] == 'x') { + return strtoul(&str[2], NULL, 16); + } else { + return strtoul(str, NULL, 10); + } +} + +static bool is_device_type_supported(uint32_t device_type_id) { + for (const auto &handler : device_handlers) { + if (handler.device_type_id == device_type_id) { + return true; + } + } + return false; +} + +static esp_err_t reset_bridge_handler(int argc, char *argv[]) +{ + ESP_RETURN_ON_FALSE(argc == 0, ESP_ERR_INVALID_ARG, TAG, "Incorrect arguments"); + esp_matter_bridge::factory_reset(); + esp_matter::factory_reset(); + return ESP_OK; +} + +static esp_err_t list_endpoint_handler(int argc, char *argv[]) +{ + ESP_RETURN_ON_FALSE(argc == 0, ESP_ERR_INVALID_ARG, TAG, "Incorrect arguments"); + uint16_t matter_endpoint_id_array[MAX_BRIDGED_DEVICE_COUNT]; + ESP_RETURN_ON_ERROR(esp_matter_bridge::get_bridged_endpoint_ids(matter_endpoint_id_array), TAG, + "Failed to get bridged endpoint id"); + ESP_LOGI(TAG, "Bridged endpoint id:"); + for (size_t idx = 0; idx < MAX_BRIDGED_DEVICE_COUNT; ++idx) { + if (matter_endpoint_id_array[idx] != chip::kInvalidEndpointId) { + ESP_LOGI(TAG, "id: %d", matter_endpoint_id_array[idx]); + } + } + return ESP_OK; +} + +static esp_err_t list_support_handler(int argc, char *argv[]) +{ + ESP_RETURN_ON_FALSE(argc == 0, ESP_ERR_INVALID_ARG, TAG, "Incorrect arguments"); + ESP_LOGI(TAG, "Supported device type:"); + for (const auto &handler : device_handlers) { + ESP_LOGI(TAG, "Device type id 0x%04" PRIX32 ", name: %s", handler.device_type_id, handler.device_type_name); + } + return ESP_OK; +} + +static esp_err_t remove_bridge_device_handler(int argc, char *argv[]) +{ + ESP_RETURN_ON_FALSE(argc == 1, ESP_ERR_INVALID_ARG, TAG, "Incorrect arguments"); + uint16_t endpoint_id = (uint16_t)string_to_uint32(argv[0]); + cli_bridged_device_t *previous_device = NULL; + cli_bridged_device_t *current_device = cli_device; + while (current_device) { + if (current_device->device->persistent_info.device_endpoint_id == endpoint_id) { + break; + } + previous_device = current_device; + current_device = current_device->next; + } + if (!current_device) { + ESP_LOGE(TAG, "No endpoint was found with given endpoint id 0x%04" PRIX16, endpoint_id); + list_endpoint_handler(0, NULL); + return ESP_ERR_NOT_FOUND; + } + + ESP_RETURN_ON_ERROR(esp_matter_bridge::remove_device(current_device->device), TAG, + "Failed to remove bridged device"); + if (!previous_device) { + cli_device = current_device->next; + } else { + previous_device->next = current_device->next; + } + esp_matter_mem_free(current_device); + ESP_LOGI(TAG, "Removed bridged device (endpoint id 0x%04" PRIX16 ") successfully", endpoint_id); + return ESP_OK; +} + +static esp_err_t add_bridge_device_handler(int argc, char *argv[]) +{ + ESP_RETURN_ON_FALSE(argc == 2, ESP_ERR_INVALID_ARG, TAG, "Incorrect arguments"); + node_t *node = node::get(); + uint16_t parent_endpoint_id = (uint16_t)string_to_uint32(argv[0]); + uint32_t device_type_id = string_to_uint32(argv[1]); + + if (!is_device_type_supported(device_type_id)) { + ESP_LOGE(TAG, "Unsupported bridged device type"); + return ESP_ERR_INVALID_ARG; + } + + cli_bridged_device_t *new_cli_dev = (cli_bridged_device_t *)esp_matter_mem_calloc(1, sizeof(cli_bridged_device_t)); + ESP_RETURN_ON_FALSE(new_cli_dev != NULL, ESP_ERR_NO_MEM, TAG, "Failed to allocate memory for bridged device"); + + new_cli_dev->device = esp_matter_bridge::create_device(node, parent_endpoint_id, device_type_id, NULL); + if (!new_cli_dev->device) { + ESP_LOGE(TAG, "Failed to create bridged device"); + esp_matter_mem_free(new_cli_dev); + return ESP_ERR_NO_MEM; + } + + if (esp_matter::endpoint::enable(new_cli_dev->device->endpoint) != ESP_OK) { + esp_matter_bridge::remove_device(new_cli_dev->device); + esp_matter_mem_free(new_cli_dev); + ESP_LOGE(TAG, "Failed to enable endpoint"); + return ESP_ERR_INVALID_STATE; + } + + new_cli_dev->next = cli_device; + cli_device = new_cli_dev; + ESP_LOGI(TAG, "Created bridged device (endpoint id 0x%04" PRIX16 ") successfully", + new_cli_dev->device->persistent_info.device_endpoint_id); + return ESP_OK; +} + +static esp_err_t device_type_callback(esp_matter::endpoint_t *ep, uint32_t device_type_id, void *priv_data) +{ + for (const auto &handler : device_handlers) { + if (handler.device_type_id == device_type_id) { + return handler.add_fun(ep); + } + } + + ESP_LOGE(TAG, "Unsupported bridged device type"); + return ESP_ERR_INVALID_ARG; +} + +static esp_err_t bridge_initialize() +{ + node_t *node = node::get(); + esp_err_t err = esp_matter_bridge::initialize(node, device_type_callback); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to initialize the esp_matter_bridge"); + return err; + } + + uint16_t matter_endpoint_id_array[MAX_BRIDGED_DEVICE_COUNT]; + esp_matter_bridge::get_bridged_endpoint_ids(matter_endpoint_id_array); + for (size_t idx = 0; idx < MAX_BRIDGED_DEVICE_COUNT; ++idx) { + if (matter_endpoint_id_array[idx] != chip::kInvalidEndpointId) { + cli_bridged_device_t *new_cli_dev = + (cli_bridged_device_t *)esp_matter_mem_calloc(1, sizeof(cli_bridged_device_t)); + if (!(new_cli_dev)) { + ESP_LOGE(TAG, "Failed to allocate memory for bridged device"); + return ESP_ERR_NO_MEM; + } + + new_cli_dev->device = esp_matter_bridge::resume_device(node, matter_endpoint_id_array[idx], NULL); + if (!(new_cli_dev->device)) { + ESP_LOGE(TAG, "Failed to resume the bridged device"); + esp_matter_mem_free(new_cli_dev); + continue; + } + + if (esp_matter::endpoint::enable(new_cli_dev->device->endpoint) != ESP_OK) { + ESP_LOGE(TAG, "Failed to enable endpoint"); + esp_matter_bridge::remove_device(new_cli_dev->device); + esp_matter_mem_free(new_cli_dev); + continue; + } + new_cli_dev->next = cli_device; + cli_device = new_cli_dev; + ESP_LOGI(TAG, "Resume endpoint 0x%04" PRIX16, matter_endpoint_id_array[idx]); + } + } + return ESP_OK; +} + +static esp_err_t bridge_dispatch(int argc, char *argv[]) +{ + if (argc <= 0) { + bridge_console.for_each_command(print_description, NULL); + return ESP_OK; + } + return bridge_console.exec_command(argc, argv); +} + +esp_err_t bridge_register_commands() +{ + // Initialize bridge and try to resume bridged device + bridge_initialize(); + + static const command_t command = { + .name = "bridge", + .description = "Bridge commands. Usage: matter esp bridge .", + .handler = bridge_dispatch, + }; + + static const command_t bridge_commands[] = { + { + .name = "add", + .description = + "Add matter bridged device. Usage: matter esp bridge add .", + .handler = add_bridge_device_handler, + }, + { + .name = "remove", + .description = "Remove matter bridged device. Usage: matter esp bridge remove .", + .handler = remove_bridge_device_handler, + }, + { + .name = "list", + .description = "List bridged device endpoint id now. Usage: matter esp bridge list.", + .handler = list_endpoint_handler, + }, + { + .name = "support", + .description = "List supported device type. Usage: matter esp bridge support.", + .handler = list_support_handler, + }, + { + .name = "reset", + .description = "reset bridge. Usage: matter esp bridge reset.", + .handler = reset_bridge_handler, + }}; + bridge_console.register_commands(bridge_commands, sizeof(bridge_commands) / sizeof(command_t)); + return add_commands(&command, 1); +} +} // namespace console +} // namespace esp_matter diff --git a/components/esp_matter_bridge/esp_matter_console_bridge.h b/components/esp_matter_bridge/esp_matter_console_bridge.h new file mode 100644 index 000000000..0a11f829a --- /dev/null +++ b/components/esp_matter_bridge/esp_matter_console_bridge.h @@ -0,0 +1,26 @@ +// 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 + +namespace esp_matter { +namespace console { + +// Should be called after esp_matter::start. +// Should not coexist with application operations, such as adding matter nodes when ZigBee devices are connected. +esp_err_t bridge_register_commands(); + +} // namespace console +} // namespace esp_matter