diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8fabba1f5bfb..4f66a6d13fa4 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -156,7 +156,7 @@ repos: require_serial: true additional_dependencies: - PyYAML == 5.3.1 - - idf-build-apps~=2.0 + - idf-build-apps~=2.5 - id: sort-yaml-files name: sort yaml files entry: tools/ci/sort_yaml.py diff --git a/components/bt/CMakeLists.txt b/components/bt/CMakeLists.txt index 093592b870b3..36837c6812a7 100644 --- a/components/bt/CMakeLists.txt +++ b/components/bt/CMakeLists.txt @@ -146,6 +146,7 @@ if(CONFIG_BT_ENABLED) host/bluedroid/bta/hd/include host/bluedroid/bta/hh/include host/bluedroid/bta/jv/include + host/bluedroid/bta/pba/include host/bluedroid/bta/sdp/include host/bluedroid/bta/sys/include host/bluedroid/device/include @@ -194,6 +195,7 @@ if(CONFIG_BT_ENABLED) "host/bluedroid/api/esp_spp_api.c" "host/bluedroid/api/esp_sdp_api.c" "host/bluedroid/api/esp_l2cap_bt_api.c" + "host/bluedroid/api/esp_pbac_api.c" "host/bluedroid/bta/ar/bta_ar.c" "host/bluedroid/bta/av/bta_av_aact.c" "host/bluedroid/bta/av/bta_av_act.c" @@ -257,6 +259,10 @@ if(CONFIG_BT_ENABLED) "host/bluedroid/bta/hf_client/bta_hf_client_rfc.c" "host/bluedroid/bta/hf_client/bta_hf_client_sco.c" "host/bluedroid/bta/hf_client/bta_hf_client_sdp.c" + "host/bluedroid/bta/pba/bta_pba_client_act.c" + "host/bluedroid/bta/pba/bta_pba_client_api.c" + "host/bluedroid/bta/pba/bta_pba_client_main.c" + "host/bluedroid/bta/pba/bta_pba_client_sdp.c" "host/bluedroid/bta/sdp/bta_sdp.c" "host/bluedroid/bta/sdp/bta_sdp_act.c" "host/bluedroid/bta/sdp/bta_sdp_api.c" @@ -299,6 +305,7 @@ if(CONFIG_BT_ENABLED) "host/bluedroid/btc/profile/std/spp/btc_spp.c" "host/bluedroid/btc/profile/std/sdp/btc_sdp.c" "host/bluedroid/btc/profile/std/l2cap/btc_l2cap.c" + "host/bluedroid/btc/profile/std/pba/btc_pba_client.c" "host/bluedroid/device/bdaddr.c" "host/bluedroid/device/controller.c" "host/bluedroid/device/interop.c" @@ -411,6 +418,7 @@ if(CONFIG_BT_ENABLED) "host/bluedroid/stack/obex/obex_api.c" "host/bluedroid/stack/obex/obex_main.c" "host/bluedroid/stack/obex/obex_tl_l2cap.c" + "host/bluedroid/stack/obex/obex_tl_rfcomm.c" "host/bluedroid/stack/rfcomm/port_api.c" "host/bluedroid/stack/rfcomm/port_rfc.c" "host/bluedroid/stack/rfcomm/port_utils.c" diff --git a/components/bt/common/btc/core/btc_task.c b/components/bt/common/btc/core/btc_task.c index 839b3741bcbd..0049328f50a8 100644 --- a/components/bt/common/btc/core/btc_task.c +++ b/components/bt/common/btc/core/btc_task.c @@ -57,6 +57,9 @@ #if BTC_HH_INCLUDED == TRUE #include "btc_hh.h" #endif /* BTC_HH_INCLUDED */ +#if BTC_PBA_CLIENT_INCLUDED +#include "btc_pba_client.h" +#endif #endif /* #if CLASSIC_BT_INCLUDED */ #endif @@ -155,6 +158,9 @@ static const btc_func_t profile_tab[BTC_PID_NUM] = { #if BTC_HH_INCLUDED [BTC_PID_HH] = {btc_hh_call_handler, btc_hh_cb_handler }, #endif +#if BTC_PBA_CLIENT_INCLUDED + [BTC_PID_PBA_CLIENT] = {btc_pba_client_call_handler, btc_pba_client_cb_handler}, +#endif #endif /* #if CLASSIC_BT_INCLUDED */ #endif #if CONFIG_BLE_MESH diff --git a/components/bt/common/btc/include/btc/btc_task.h b/components/bt/common/btc/include/btc/btc_task.h index 232186b51c5b..166b7e0f1631 100644 --- a/components/bt/common/btc/include/btc/btc_task.h +++ b/components/bt/common/btc/include/btc/btc_task.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -67,6 +67,9 @@ typedef enum { #if (BTC_HF_CLIENT_INCLUDED == TRUE) BTC_PID_HF_CLIENT, #endif /* BTC_HF_CLIENT_INCLUDED */ +#if (BTC_PBA_CLIENT_INCLUDED == TRUE) + BTC_PID_PBA_CLIENT, +#endif /* BTC_PBA_CLIENT_INCLUDED */ #endif /* CLASSIC_BT_INCLUDED */ #if CONFIG_BLE_MESH BTC_PID_PROV, @@ -123,8 +126,8 @@ extern "C" { /** * transfer an message to another module in the different task. * @param msg message - * @param arg paramter - * @param arg_len length of paramter + * @param arg parameter + * @param arg_len length of parameter * @param copy_func deep copy function * @param free_func deep free function * @return BT_STATUS_SUCCESS: success @@ -134,7 +137,7 @@ bt_status_t btc_transfer_context(btc_msg_t *msg, void *arg, int arg_len, btc_arg btc_arg_deep_free_t free_func); /** - * transfer an message to another module in tha same task. + * transfer an message to another module in the same task. * @param msg message * @return BT_STATUS_SUCCESS: success * others: fail diff --git a/components/bt/host/bluedroid/Kconfig.in b/components/bt/host/bluedroid/Kconfig.in index dc235c2b9975..2c6412d862f6 100644 --- a/components/bt/host/bluedroid/Kconfig.in +++ b/components/bt/host/bluedroid/Kconfig.in @@ -191,6 +191,29 @@ config BT_HID_DEVICE_ENABLED help This enables the BT HID Device +menuconfig BT_PBAC_ENABLED + bool "PBAP Client" + depends on BT_CLASSIC_ENABLED + default n + select BT_GOEPC_ENABLED + help + This enables the Phone Book Access Profile Client + +config BT_PBAC_SUPPORTED_FEAT + hex "PBAP Client Supported Features" + depends on BT_PBAC_ENABLED + default 0x000003FF + help + Set the supported features of PBAP Client, the default value is supported all features + +config BT_PBAC_PREFERRED_MTU + int "PBAP Client Preferred MTU" + depends on BT_PBAC_ENABLED + default 0 + help + MTU is limited by the max MTU of transport layer, and should not be smaller than 255, + but can be set to zero to use a default MTU of transport layer + config BT_GOEPC_ENABLED bool depends on BT_CLASSIC_ENABLED diff --git a/components/bt/host/bluedroid/api/esp_pbac_api.c b/components/bt/host/bluedroid/api/esp_pbac_api.c new file mode 100644 index 000000000000..3799038f94be --- /dev/null +++ b/components/bt/host/bluedroid/api/esp_pbac_api.c @@ -0,0 +1,251 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include "esp_err.h" +#include "esp_bt_main.h" +#include "esp_pbac_api.h" +#include "btc/btc_manage.h" +#include "btc_pba_client.h" + +#if BTC_PBA_CLIENT_INCLUDED + +esp_err_t esp_pbac_init(void) +{ + if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { + return ESP_ERR_INVALID_STATE; + } + + btc_msg_t msg; + + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_PBA_CLIENT; + msg.act = BTC_PBA_CLIENT_INIT_EVT; + + /* Switch to BTC context */ + bt_status_t stat = btc_transfer_context(&msg, NULL, 0, NULL, NULL); + return (stat == BT_STATUS_SUCCESS) ? ESP_OK : ESP_FAIL; +} + +esp_err_t esp_pbac_deinit(void) +{ + if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { + return ESP_ERR_INVALID_STATE; + } + + btc_msg_t msg; + + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_PBA_CLIENT; + msg.act = BTC_PBA_CLIENT_DEINIT_EVT; + + /* Switch to BTC context */ + bt_status_t stat = btc_transfer_context(&msg, NULL, 0, NULL, NULL); + return (stat == BT_STATUS_SUCCESS) ? ESP_OK : ESP_FAIL; +} + +esp_err_t esp_pbac_register_callback(esp_pbac_callback_t callback) +{ + if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { + return ESP_ERR_INVALID_STATE; + } + + if (callback == NULL) { + return ESP_FAIL; + } + + btc_profile_cb_set(BTC_PID_PBA_CLIENT, callback); + return ESP_OK; +} + +esp_err_t esp_pbac_connect(esp_bd_addr_t bd_addr) +{ + if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { + return ESP_ERR_INVALID_STATE; + } + + if (bd_addr == NULL) { + return ESP_ERR_INVALID_ARG; + } + + btc_msg_t msg; + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_PBA_CLIENT; + msg.act = BTC_PBA_CLIENT_CONNECT_EVT; + + btc_pba_client_args_t args = {0}; + memcpy(args.connect.bd_addr.address, bd_addr, sizeof(esp_bd_addr_t)); + + /* Switch to BTC context */ + bt_status_t stat = btc_transfer_context(&msg, &args, sizeof(btc_pba_client_args_t), NULL, NULL); + return (stat == BT_STATUS_SUCCESS) ? ESP_OK : ESP_FAIL; +} + +esp_err_t esp_pbac_disconnect(esp_pbac_conn_hdl_t handle) +{ + if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { + return ESP_ERR_INVALID_STATE; + } + + if (handle == ESP_PBAC_INVALID_HANDLE) { + return ESP_ERR_INVALID_ARG; + } + + btc_msg_t msg; + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_PBA_CLIENT; + msg.act = BTC_PBA_CLIENT_DISCONNECT_EVT; + + btc_pba_client_args_t args = {0}; + args.disconnect.handle = handle; + + /* Switch to BTC context */ + bt_status_t stat = btc_transfer_context(&msg, &args, sizeof(btc_pba_client_args_t), NULL, NULL); + return (stat == BT_STATUS_SUCCESS) ? ESP_OK : ESP_FAIL; +} + + +esp_err_t esp_pbac_pull_phone_book(esp_pbac_conn_hdl_t handle, const char *name, esp_pbac_pull_phone_book_app_param_t *app_param) +{ + if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { + return ESP_ERR_INVALID_STATE; + } + + if (handle == ESP_PBAC_INVALID_HANDLE || name == NULL) { + return ESP_ERR_INVALID_ARG; + } + + btc_msg_t msg; + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_PBA_CLIENT; + msg.act = BTC_PBA_CLIENT_PULL_PHONE_BOOK_EVT; + + btc_pba_client_args_t args = {0}; + args.pull_phone_book.handle = handle; + args.pull_phone_book.name = (char *)name; + if (app_param != NULL) { + args.pull_phone_book.include_app_param = true; + memcpy(&args.pull_phone_book.app_param, app_param, sizeof(esp_pbac_pull_phone_book_app_param_t)); + } + else { + args.pull_phone_book.include_app_param = false; + } + + /* Switch to BTC context */ + bt_status_t stat = btc_transfer_context(&msg, &args, sizeof(btc_pba_client_args_t), btc_pba_client_args_deep_copy, btc_pba_client_args_deep_free); + return (stat == BT_STATUS_SUCCESS) ? ESP_OK : ESP_FAIL; +} + +esp_err_t esp_pbac_set_phone_book(esp_pbac_conn_hdl_t handle, esp_pbac_set_phone_book_flags_t flags, const char *name) +{ + if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { + return ESP_ERR_INVALID_STATE; + } + + /* since ESP_PBAC_SET_PHONE_BOOK_FLAGS_ROOT is equal to ESP_PBAC_SET_PHONE_BOOK_FLAGS_DOWN, we dont check XXX_DOWN */ + if (handle == ESP_PBAC_INVALID_HANDLE || (flags != ESP_PBAC_SET_PHONE_BOOK_FLAGS_ROOT && flags != ESP_PBAC_SET_PHONE_BOOK_FLAGS_UP)) { + return ESP_ERR_INVALID_ARG; + } + + btc_msg_t msg; + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_PBA_CLIENT; + msg.act = BTC_PBA_CLIENT_SET_PHONE_BOOK_EVT; + + btc_pba_client_args_t args = {0}; + args.set_phone_book.handle = handle; + args.set_phone_book.flags = flags; + /* set phone book name is allowed to be NULL */ + args.set_phone_book.name = (char *)name; + + /* Switch to BTC context */ + bt_status_t stat = btc_transfer_context(&msg, &args, sizeof(btc_pba_client_args_t), btc_pba_client_args_deep_copy, btc_pba_client_args_deep_free); + return (stat == BT_STATUS_SUCCESS) ? ESP_OK : ESP_FAIL; +} + +esp_err_t esp_pbac_set_phone_book2(esp_pbac_conn_hdl_t handle, const char *path) +{ + if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { + return ESP_ERR_INVALID_STATE; + } + + btc_msg_t msg; + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_PBA_CLIENT; + msg.act = BTC_PBA_CLIENT_SET_PHONE_BOOK2_EVT; + + btc_pba_client_args_t args = {0}; + args.set_phone_book.handle = handle; + args.set_phone_book.name = (char *)path; + + /* Switch to BTC context */ + bt_status_t stat = btc_transfer_context(&msg, &args, sizeof(btc_pba_client_args_t), btc_pba_client_args_deep_copy, btc_pba_client_args_deep_free); + return (stat == BT_STATUS_SUCCESS) ? ESP_OK : ESP_FAIL; +} + +esp_err_t esp_pbac_pull_vcard_listing(esp_pbac_conn_hdl_t handle, const char *name, esp_pbac_pull_vcard_listing_app_param_t *app_param) +{ + if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { + return ESP_ERR_INVALID_STATE; + } + + if (handle == ESP_PBAC_INVALID_HANDLE || name == NULL) { + return ESP_ERR_INVALID_ARG; + } + + btc_msg_t msg; + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_PBA_CLIENT; + msg.act = BTC_PBA_CLIENT_PULL_VCARD_LISTING_EVT; + + btc_pba_client_args_t args = {0}; + args.pull_vcard_listing.handle = handle; + args.pull_vcard_listing.name = (char *)name; + if (app_param != NULL) { + args.pull_vcard_listing.include_app_param = true; + memcpy(&args.pull_vcard_listing.app_param, app_param, sizeof(esp_pbac_pull_vcard_listing_app_param_t)); + } + else { + args.pull_vcard_listing.include_app_param = false; + } + + /* Switch to BTC context */ + bt_status_t stat = btc_transfer_context(&msg, &args, sizeof(btc_pba_client_args_t), btc_pba_client_args_deep_copy, btc_pba_client_args_deep_free); + return (stat == BT_STATUS_SUCCESS) ? ESP_OK : ESP_FAIL; +} + +esp_err_t esp_pbac_pull_vcard_entry(esp_pbac_conn_hdl_t handle, const char *name, esp_pbac_pull_vcard_entry_app_param_t *app_param) +{ + if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { + return ESP_ERR_INVALID_STATE; + } + + if (handle == ESP_PBAC_INVALID_HANDLE || name == NULL) { + return ESP_ERR_INVALID_ARG; + } + + btc_msg_t msg; + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_PBA_CLIENT; + msg.act = BTC_PBA_CLIENT_PULL_VCARD_ENTRY_EVT; + + btc_pba_client_args_t args = {0}; + args.pull_vcard_entry.handle = handle; + args.pull_vcard_entry.name = (char *)name; + if (app_param != NULL) { + args.pull_vcard_entry.include_app_param = true; + memcpy(&args.pull_vcard_entry.app_param, app_param, sizeof(esp_pbac_pull_vcard_entry_app_param_t)); + } + else { + args.pull_vcard_entry.include_app_param = false; + } + + /* Switch to BTC context */ + bt_status_t stat = btc_transfer_context(&msg, &args, sizeof(btc_pba_client_args_t), btc_pba_client_args_deep_copy, btc_pba_client_args_deep_free); + return (stat == BT_STATUS_SUCCESS) ? ESP_OK : ESP_FAIL; +} + +#endif diff --git a/components/bt/host/bluedroid/api/include/api/esp_pba_defs.h b/components/bt/host/bluedroid/api/include/api/esp_pba_defs.h new file mode 100644 index 000000000000..c3a6f5109ee7 --- /dev/null +++ b/components/bt/host/bluedroid/api/include/api/esp_pba_defs.h @@ -0,0 +1,27 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "esp_bt_defs.h" + +/* Supported repositories bit mask */ +#define ESP_PBA_SUPPORTED_REPO_LOCAL_PHONE_BOOK 0x01 +#define ESP_PBA_SUPPORTED_REPO_SIM_CARD 0x02 +#define ESP_PBA_SUPPORTED_REPO_SPEED_DIAL 0x04 +#define ESP_PBA_SUPPORTED_REPO_FAVORITES 0x08 + +/* Supported features bit mask */ +#define ESP_PBA_SUPPORTED_FEAT_DOWNLOAD 0x0001 +#define ESP_PBA_SUPPORTED_FEAT_BROWSING 0x0002 +#define ESP_PBA_SUPPORTED_FEAT_DATABASE_IDENTIFIER 0x0004 +#define ESP_PBA_SUPPORTED_FEAT_FOLDER_VERSION_COUNTERS 0x0008 +#define ESP_PBA_SUPPORTED_FEAT_VCARD_SELECTING 0x0010 +#define ESP_PBA_SUPPORTED_FEAT_ENHANCED_MISSED_CALLS 0x0020 +#define ESP_PBA_SUPPORTED_FEAT_X_BT_UCI_VCARD_PROPERTY 0x0040 +#define ESP_PBA_SUPPORTED_FEAT_X_BT_UID_VCARD_PROPERTY 0x0080 +#define ESP_PBA_SUPPORTED_FEAT_CONTACT_REFERENCING 0x0100 +#define ESP_PBA_SUPPORTED_FEAT_DEFAULT_CONTACT_IMAGE_FORMAT 0x0200 diff --git a/components/bt/host/bluedroid/api/include/api/esp_pbac_api.h b/components/bt/host/bluedroid/api/include/api/esp_pbac_api.h new file mode 100644 index 000000000000..131d53f85966 --- /dev/null +++ b/components/bt/host/bluedroid/api/include/api/esp_pbac_api.h @@ -0,0 +1,340 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "esp_err.h" +#include "esp_bt_defs.h" +#include "esp_pba_defs.h" + +#define ESP_PBAC_INVALID_HANDLE 0 /*!< invalid handle value */ + +typedef uint16_t esp_pbac_conn_hdl_t; + +/** + * @brief PBA client callback events + */ +typedef enum { + ESP_PBAC_INIT_EVT, /*!< PBA client initialized event */ + ESP_PBAC_DEINIT_EVT, /*!< PBA client de-initialized event */ + ESP_PBAC_CONNECTION_STATE_EVT, /*!< PBA client connection state changed event */ + ESP_PBAC_PULL_PHONE_BOOK_RESPONSE_EVT, /*!< Response of pull phone book */ + ESP_PBAC_SET_PHONE_BOOK_RESPONSE_EVT, /*!< Response of set phone book */ + ESP_PBAC_PULL_VCARD_LISTING_RESPONSE_EVT, /*!< Response of pull vCard listing */ + ESP_PBAC_PULL_VCARD_ENTRY_RESPONSE_EVT, /*!< Response of pull vCard entry */ +} esp_pbac_event_t; + +/** + * @brief PBA client status code + */ +typedef enum { + ESP_PBAC_SUCCESS = 0, /*!< Operation success */ + ESP_PBAC_FAILURE, /*!< Generic failure */ + ESP_PBAC_ALREADY_CONN, /*!< Connection to peer device already exist */ + ESP_PBAC_NO_RESOURCE, /*!< No more resource */ + ESP_PBAC_SDP_FAIL, /*!< Connection failed in SDP */ + ESP_PBAC_GOEP_FAIL, /*!< Operation failed in GOEP */ + ESP_PBAC_AUTH_FAIL, /*!< Connection failed in OBEX authentication */ + ESP_PBAC_DEINIT, /*!< Connection closed due to pba client is deinit */ + + /* these error code is related to OBEX */ + ESP_PBAC_BAD_REQUEST = 0xC0, /*!< Server couldn't understand request */ + ESP_PBAC_UNAUTHORIZED = 0xC1, /*!< Unauthorized */ + ESP_PBAC_FORBIDDEN = 0xC3, /*!< Operation is understood but refused */ + ESP_PBAC_NOT_FOUND = 0xC4, /*!< Not found */ + ESP_PBAC_NOT_ACCEPTABLE = 0xC6, /*!< Not Acceptable */ + ESP_PBAC_PRECONDITION_FAILED = 0xCC, /*!< Precondition failed */ + ESP_PBAC_NOT_IMPLEMENTED = 0xD1, /*!< Not implemented */ + ESP_PBAC_SERVICE_UNAVAILABLE = 0xD3, /*!< Service unavailable */ +} esp_pbac_status_t; + +/** + * @brief PBA client set phone book flags + */ +typedef enum { + ESP_PBAC_SET_PHONE_BOOK_FLAGS_ROOT = 0x02, /*!< Go back to root, name should set to empty string, not NULL */ + ESP_PBAC_SET_PHONE_BOOK_FLAGS_DOWN = 0x02, /*!< Go down 1 level, name should set to child folder */ + ESP_PBAC_SET_PHONE_BOOK_FLAGS_UP = 0x03, /*!< Go up 1 level, name is optional */ +} esp_pbac_set_phone_book_flags_t; + +/** + * @brief PBA client pull phone book optional application parameter + */ +typedef struct { + uint8_t include_property_selector : 1; /*!< 1 if app param include property_selector */ + uint8_t include_format : 1; /*!< 1 if app param include format */ + uint8_t include_max_list_count : 1; /*!< 1 if app param include max_list_count */ + uint8_t include_list_start_offset : 1; /*!< 1 if app param include list_start_offset */ + uint8_t include_reset_new_missed_calls : 1; /*!< 1 if app param include reset_new_missed_calls */ + uint8_t include_vcard_selector : 1; /*!< 1 if app param include vcard_selector */ + uint8_t include_vcard_selector_operator : 1; /*!< 1 if app param include vcard_selector_operator */ + uint8_t format; /*!< 0x00 = 2.1, 0x01 = 3.0 */ + uint8_t reset_new_missed_calls; /*!< 0x01 = Reset */ + uint8_t vcard_selector_operator; /*!< 0x00 = OR, 0x01 = AND */ + uint16_t max_list_count; /*!< 0x0000 to 0xFFFF */ + uint16_t list_start_offset; /*!< 0x0000 to 0xFFFF */ + uint64_t property_selector; /*!< 64 bits mask */ + uint64_t vcard_selector; /*!< 64 bits mask */ +} esp_pbac_pull_phone_book_app_param_t; + +/** + * @brief PBA client pull vCard listing optional application parameter + */ +typedef struct { + uint8_t include_order : 1; /*!< 1 if app param include order */ + uint8_t include_search_value : 1; /*!< 1 if app param include search_value */ + uint8_t include_search_property : 1; /*!< 1 if app param include search_property */ + uint8_t include_max_list_count : 1; /*!< 1 if app param include max_list_count */ + uint8_t include_list_start_offset : 1; /*!< 1 if app param include list_start_offset */ + uint8_t include_reset_new_missed_calls : 1; /*!< 1 if app param include reset_new_missed_calls */ + uint8_t include_vcard_selector : 1; /*!< 1 if app param include vcard_selector */ + uint8_t include_vcard_selector_operator : 1; /*!< 1 if app param include vcard_selector_operator */ + uint8_t order; /*!< 0x00 = indexed, 0x01 = alphanumeric */ + uint8_t search_property; /*!< 0x00 = Name, 0x01 = Number, 0x02 = Sound */ + uint8_t reset_new_missed_calls; /*!< 0x01 = Reset */ + uint8_t vcard_selector_operator; /*!< 0x00 = OR, 0x01 = AND */ + uint16_t max_list_count; /*!< 0x0000 to 0xFFFF */ + uint16_t list_start_offset; /*!< 0x0000 to 0xFFFF */ + char *search_value; /*!< Text */ + uint64_t vcard_selector; /*!< 64 bits mask */ +} esp_pbac_pull_vcard_listing_app_param_t; + +/** + * @brief PBA client pull vCard entry optional application parameter + */ +typedef struct { + uint8_t include_property_selector : 1; /*!< 1 if app param include property_selector */ + uint8_t include_format : 1; /*!< 1 if app param include format */ + uint8_t format; /*!< 0x00 = 2.1, 0x01 = 3.0 */ + uint64_t property_selector; /*!< 64 bits mask */ +} esp_pbac_pull_vcard_entry_app_param_t; + +/** + * @brief PBA client callback parameters + */ +typedef union { + /** + * @brief ESP_PBAC_CONNECTION_STATE_EVT + */ + struct pbac_conn_stat_param { + bool connected; /*!< whether pba client is connected to server */ + esp_pbac_conn_hdl_t handle; /*!< connection handle, non zeros if exist */ + uint8_t peer_supported_repo; /*!< peer supported repositories */ + uint32_t peer_supported_feat; /*!< peer supported features */ + esp_bd_addr_t remote_bda; /*!< remote bluetooth device address */ + esp_pbac_status_t reason; /*!< reason if disconnect */ + } conn_stat; /*!< PBA client connection status */ + + /** + * @brief ESP_PBAC_PULL_PHONE_BOOK_RESPONSE_EVT + */ + struct pbac_pull_phone_book_rsp_param { + esp_pbac_conn_hdl_t handle; /*!< PBA client connection handle */ + esp_pbac_status_t result; /*!< operation result, ESP_PBAC_SUCCESS if success */ + bool final; /*!< whether this is the final response packet */ + uint8_t *data; /*!< response data */ + uint16_t data_len; /*!< response data len */ + /* The following are the application parameters */ + uint8_t include_phone_book_size : 1; /*!< 1 if app param include phone_book_size */ + uint8_t include_new_missed_calls : 1; /*!< 1 if app param include new_missed_calls */ + uint8_t include_primary_folder_version : 1; /*!< 1 if app param include primary_folder_version */ + uint8_t include_secondary_folder_version : 1; /*!< 1 if app param include secondary_folder_version */ + uint8_t include_database_identifier : 1; /*!< 1 if app param include database_identifier */ + uint8_t new_missed_calls; /*!< 0x00 to 0xFF */ + uint16_t phone_book_size; /*!< 0x0000 to 0xFFFF */ + uint8_t *primary_folder_version; /*!< 0 to (2^128 -1) */ + uint8_t *secondary_folder_version; /*!< 0 to (2^128 -1) */ + uint8_t *database_identifier; /*!< 0 to (2^128 -1) */ + } pull_phone_book_rsp; /*!< pull phone book response */ + + /** + * @brief ESP_PBAC_SET_PHONE_BOOK_RESPONSE_EVT + */ + struct pbac_set_phone_book_rsp_param { + esp_pbac_conn_hdl_t handle; /*!< PBA client connection handle */ + esp_pbac_status_t result; /*!< operation result, ESP_PBAC_SUCCESS if success */ + } set_phone_book_rsp; /*!< set phone book response, always the final response */ + + /** + * @brief ESP_PBAC_PULL_VCARD_LISTING_RESPONSE_EVT + */ + struct pbac_pull_vcard_listing_rsp_param { + esp_pbac_conn_hdl_t handle; /*!< PBA client connection handle */ + esp_pbac_status_t result; /*!< operation result, ESP_PBAC_SUCCESS if success */ + bool final; /*!< whether this is the final response packet */ + uint8_t *data; /*!< response data */ + uint16_t data_len; /*!< response data len */ + /* The following are the application parameters */ + uint8_t include_phone_book_size : 1; /*!< 1 if app param include phone_book_size */ + uint8_t include_new_missed_calls : 1; /*!< 1 if app param include new_missed_calls */ + uint8_t include_primary_folder_version : 1; /*!< 1 if app param include primary_folder_version */ + uint8_t include_secondary_folder_version : 1; /*!< 1 if app param include secondary_folder_version */ + uint8_t include_database_identifier : 1; /*!< 1 if app param include database_identifier */ + uint8_t new_missed_calls; /*!< 0x00 to 0xFF */ + uint16_t phone_book_size; /*!< 0x0000 to 0xFFFF */ + uint8_t *primary_folder_version; /*!< 0 to (2^128 -1) */ + uint8_t *secondary_folder_version; /*!< 0 to (2^128 -1) */ + uint8_t *database_identifier; /*!< 0 to (2^128 -1) */ + } pull_vcard_listing_rsp; /*!< pull vcard listing response */ + + /** + * @brief ESP_PBAC_PULL_VCARD_ENTRY_RESPONSE_EVT + */ + struct pbac_pull_vcard_entry_rsp_param { + esp_pbac_conn_hdl_t handle; /*!< PBA client connection handle */ + esp_pbac_status_t result; /*!< operation result, ESP_PBAC_SUCCESS if success */ + bool final; /*!< whether this is the final response packet */ + uint8_t *data; /*!< response data */ + uint16_t data_len; /*!< response data len */ + /* The following are the application parameters */ + uint8_t include_database_identifier : 1; /*!< 1 if app param include database_identifier */ + uint8_t *database_identifier; /*!< 0 to (2^128 -1) */ + } pull_vcard_entry_rsp; /*!< pull vcard listing response */ +} esp_pbac_param_t; + +/** + * @brief PBA client callback function type + * + * @param event : Event type + * + * @param param : Pointer to callback parameter + */ +typedef void (*esp_pbac_callback_t)(esp_pbac_event_t event, esp_pbac_param_t *param); + + +/** + * @brief This function is called to register a user callbacks in PBA client. + * + * @param[in] callback: pointer to the user callback function. + * + * @return + * - ESP_OK: success + * - other: failed + */ +esp_err_t esp_pbac_register_callback(esp_pbac_callback_t callback); + +/** + * @brief Initializes PBA client interface. This function should be called after bluedroid + * enable successfully, and should be called after esp_pbac_register_callback. + * + * @return + * - ESP_OK: success + * - other: failed + */ +esp_err_t esp_pbac_init(void); + +/** + * @brief De-initializes PBA client interface. This will close all PBA client connection. + * + * @return + * - ESP_OK: success + * - other: failed + */ +esp_err_t esp_pbac_deinit(void); + +/** + * @brief Start the process to establish a connection to PBA server. + * + * @param[in] bd_addr: peer bluetooth device address + * + * @return + * - ESP_OK: success + * - other: failed + */ +esp_err_t esp_pbac_connect(esp_bd_addr_t bd_addr); + +/** + * @brief Disconnects from the current connected PBA server. + * + * @param[in] handle: connection handle retrieved from ESP_PBAC_CONNECTION_STATE_EVT + * + * @return + * - ESP_OK: success + * - other: failed + */ +esp_err_t esp_pbac_disconnect(esp_pbac_conn_hdl_t handle); + +/** + * @brief Send a request to pull phone book. + * + * @param[in] handle: connection handle retrieved from ESP_PBAC_CONNECTION_STATE_EVT + * + * @param[in] name: phone book object path and name, shall contain the absolute path + * in the virtual folder architecture + * + * @param[in] app_param: optional application parameter + * + * @return + * - ESP_OK: success + * - other: failed + */ +esp_err_t esp_pbac_pull_phone_book(esp_pbac_conn_hdl_t handle, const char *name, esp_pbac_pull_phone_book_app_param_t *app_param); + +/** + * @brief Send a request to set the current folder in the virtual folder architecture. + * + * @param[in] handle: connection handle retrieved from ESP_PBAC_CONNECTION_STATE_EVT + * + * @param[in] flags: operation flags, one of ESP_PBAC_SET_PHONE_BOOK_FLAGS_XXX + * + * @param[in] name: folder name, if flags is set to ROOT, name should be empty string (""), + * if flags is set to UP, name is optional, if flags is set to DOWN, name + * is mandatory + * + * @return + * - ESP_OK: success + * - other: failed + */ +esp_err_t esp_pbac_set_phone_book(esp_pbac_conn_hdl_t handle, esp_pbac_set_phone_book_flags_t flags, const char *name); + +/** + * @brief Set the current folder in the virtual folder architecture, use absolute path. + * + * @param[in] handle: connection handle retrieved from ESP_PBAC_CONNECTION_STATE_EVT + * + * @param[in] path: absolute path of the folder intend to set. NULL or empty string will + * set to ROOT + * + * @return + * - ESP_OK: success + * - other: failed + */ +esp_err_t esp_pbac_set_phone_book2(esp_pbac_conn_hdl_t handle, const char *path); + +/** + * @brief Send a request to pull vCard listing. + * + * @param[in] handle: connection handle retrieved from ESP_PBAC_CONNECTION_STATE_EVT + * + * @param[in] name: specifies the name of the folder to be retrieved, uses relative paths, + * shall not include any path information. An empty name (empty string "") + * may be sent to retrieve the vCard Listing object of the current folder. + * However, it is illegal to issue a pull vCard listing request with an + * empty name header from the ‘telecom/’ folder + * + * @param[in] app_param: optional application parameter + * + * @return + * - ESP_OK: success + * - other: failed + */ +esp_err_t esp_pbac_pull_vcard_listing(esp_pbac_conn_hdl_t handle, const char *name, esp_pbac_pull_vcard_listing_app_param_t *app_param); + +/** + * @brief Send a request to pull vCard entry. + * + * @param[in] handle: connection handle retrieved from ESP_PBAC_CONNECTION_STATE_EVT + * + * @param[in] name: vCard name or, if supported, the X-BT-UID of the object to be retrieved. + * uses relative paths,shall not include any path information + * + * @param[in] app_param: optional application parameter + * + * @return + * - ESP_OK: success + * - other: failed + */ +esp_err_t esp_pbac_pull_vcard_entry(esp_pbac_conn_hdl_t handle, const char *name, esp_pbac_pull_vcard_entry_app_param_t *app_param); diff --git a/components/bt/host/bluedroid/bta/av/bta_av_ca_act.c b/components/bt/host/bluedroid/bta/av/bta_av_ca_act.c index a2fdcbaddc5b..d886f593d182 100644 --- a/components/bt/host/bluedroid/bta/av/bta_av_ca_act.c +++ b/components/bt/host/bluedroid/bta/av/bta_av_ca_act.c @@ -278,6 +278,7 @@ void bta_av_ca_api_get(tBTA_AV_RCB *p_rcb, tBTA_AV_DATA *p_data) GOEPC_RequestAddHeader(p_rcb->cover_art_goep_hdl, COVER_ART_HEADER_ID_IMG_HANDLE, (UINT8 *)image_handle_utf16, BTA_AV_CA_IMG_HDL_UTF16_LEN); if (p_data->api_ca_get.type == BTA_AV_CA_GET_IMAGE) { GOEPC_RequestAddHeader(p_rcb->cover_art_goep_hdl, COVER_ART_HEADER_ID_IMG_DESCRIPTOR, (UINT8 *)p_data->api_ca_get.image_descriptor, p_data->api_ca_get.image_descriptor_len); + osi_free(p_data->api_ca_get.image_descriptor); } /* always request to enable srm */ GOEPC_RequestSetSRM(p_rcb->cover_art_goep_hdl, TRUE, FALSE); diff --git a/components/bt/host/bluedroid/bta/include/bta/bta_pba_client_api.h b/components/bt/host/bluedroid/bta/include/bta/bta_pba_client_api.h new file mode 100644 index 000000000000..404f8ffd81fe --- /dev/null +++ b/components/bt/host/bluedroid/bta/include/bta/bta_pba_client_api.h @@ -0,0 +1,92 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "common/bt_target.h" +#include "stack/obex_api.h" +#include "bta/bta_pba_defs.h" + +#if BTA_PBA_CLIENT_INCLUDED + +/* Phone Book Access Profile (client) version */ +#define PBAP_PCE_VERSION 0x102 /* v1.2 */ +#define PBAP_PCE_SUPPORTED_FEATURES 0x0000003F /* support all features */ + +/* PBA Client callback events */ +#define BTA_PBA_CLIENT_ENABLE_EVT 0 /* PBA Client enabled */ +#define BTA_PBA_CLIENT_DISABLE_EVT 1 /* PBA Client disabled */ +#define BTA_PBA_CLIENT_REGISTER_EVT 2 /* PBA Client registered */ +#define BTA_PBA_CLIENT_DEREGISTER_EVT 3 /* PBA Client deregistered */ +#define BTA_PBA_CLIENT_CONN_OPEN_EVT 4 /* PBA Client connection opened */ +#define BTA_PBA_CLIENT_CONN_CLOSE_EVT 5 /* PBA Client connection closed */ +#define BTA_PBA_CLIENT_PULL_PHONE_BOOK_RSP_EVT 6 /* PBA Client pull phone book response */ +#define BTA_PBA_CLIENT_SET_PHONE_BOOK_RSP_EVT 7 /* PBA Client set phone book response */ +#define BTA_PBA_CLIENT_PULL_VCARD_LISTING_RSP_EVT 8 /* PBA Client pull vCard listing response */ +#define BTA_PBA_CLIENT_PULL_VCARD_ENTRY_RSP_EVT 9 /* PBA Client pull vCard entry response */ + +typedef enum { + BTA_PBA_CLIENT_NO_ERROR, /* operation success */ + BTA_PBA_CLIENT_FAIL, /* general fail */ + BTA_PBA_CLIENT_ALREADY_CONN, /* connection to peer device already exist */ + BTA_PBA_CLIENT_NO_RESOURCE, /* no resource */ + BTA_PBA_CLIENT_SDP_ERROR, /* error in sdp */ + BTA_PBA_CLIENT_GOEP_ERROR, /* error in goep */ + BTA_PBA_CLIENT_AUTH_FAIL, /* authenticate failed */ + BTA_PBA_CLIENT_DISABLE, /* operation failed due to pba client is disable */ + + /* these error code is related to OBEX */ + BTA_PBA_CLIENT_BAD_REQUEST = 0xC0, /* Server couldn't understand request */ + BTA_PBA_CLIENT_UNAUTHORIZED = 0xC1, /* Unauthorized */ + BTA_PBA_CLIENT_FORBIDDEN = 0xC3, /* Operation is understood but refused > */ + BTA_PBA_CLIENT_NOT_FOUND = 0xC4, /* Not found */ + BTA_PBA_CLIENT_NOT_ACCEPTABLE = 0xC6, /* Not Acceptable */ + BTA_PBA_CLIENT_PRECONDITION_FAILED = 0xCC, /* Precondition failed */ + BTA_PBA_CLIENT_NOT_IMPLEMENTED = 0xD1, /* Not implemented */ + BTA_PBA_CLIENT_SERVICE_UNAVAILABLE = 0xD3, /* Service unavailable */ +} tBTA_PBA_CLIENT_ERR; + +typedef struct { + UINT16 handle; /* connection handle */ + tBTA_PBA_CLIENT_ERR error; /* error code */ + BD_ADDR bd_addr; /* peer BD addr */ + UINT8 peer_supported_repo; /* peer supported repositories */ + UINT32 peer_supported_feat; /* peer supported feature */ +} tBTA_PBA_CLIENT_CONN; + +typedef struct { + tBTA_PBA_CLIENT_ERR status; + UINT16 handle; /* connection handle */ + BOOLEAN final; /* final data event */ + UINT8 *data; /* body data in response packet */ + UINT16 data_len; /* body data length */ + UINT8 *app_param; /* application parameters */ + UINT16 app_param_len; /* application parameters length */ + BT_HDR *pkt; /* raw buff that store all data, should be freed before return */ +} tBTA_PBA_CLIENT_RESPONSE; + +/* union of data associated with PBA Client callback */ +typedef union { + tBTA_PBA_CLIENT_CONN conn; /* CONN_OPEN_EVT, CONN_CLOSE_EVT */ + tBTA_PBA_CLIENT_RESPONSE response; /* XXX_RSP_EVT */ +} tBTA_PBA_CLIENT; + +typedef UINT8 tBTA_PBA_CLIENT_EVT; + +typedef void (tBTA_PBA_CLIENT_CBACK)(tBTA_PBA_CLIENT_EVT event, tBTA_PBA_CLIENT *p_data); + +void BTA_PbaClientEnable(tBTA_PBA_CLIENT_CBACK *p_cback); +void BTA_PbaClientDisable(void); +void BTA_PbaClientRegister(const char *server_name); +void BTA_PbaClientDeregister(void); +void BTA_PbaClientOpen(BD_ADDR bd_addr, tBTA_SEC sec_mask, UINT32 supported_feat, UINT16 mtu); +void BTA_PbaClientClose(UINT16 handle); +void BTA_PbaClientPullPhoneBook(UINT16 handle, char *name, UINT8 *app_param, UINT16 app_param_len); +void BTA_PbaClientSetPhoneBook(UINT16 handle, UINT8 flags, char *name); +void BTA_PbaClientPullvCardListing(UINT16 handle, char *name, UINT8 *app_param, UINT16 app_param_len); +void BTA_PbaClientPullvCardEntry(UINT16 handle, char *name, UINT8 *app_param, UINT16 app_param_len); + +#endif diff --git a/components/bt/host/bluedroid/bta/include/bta/bta_pba_defs.h b/components/bt/host/bluedroid/bta/include/bta/bta_pba_defs.h new file mode 100644 index 000000000000..ce92f9cd3be5 --- /dev/null +++ b/components/bt/host/bluedroid/bta/include/bta/bta_pba_defs.h @@ -0,0 +1,93 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +/* PBAP supported repositories */ +#define BTA_PBAP_REPO_LOCAL_PHONEBOOK 0x01 +#define BTA_PBAP_REPO_SIM_CARD 0x02 +#define BTA_PBAP_REPO_SPEED_DIAL 0x04 +#define BTA_PBAP_REPO_FAVORITES 0x08 + +/* PBAP supported features */ +#define BTA_PBAP_FEAT_DOWNLOAD 0x0001 +#define BTA_PBAP_FEAT_BROWSING 0x0002 +#define BTA_PBAP_FEAT_DATABASE_IDENTIFIER 0x0004 +#define BTA_PBAP_FEAT_FLODER_VERSION_COUNTER 0x0008 +#define BTA_PBAP_FEAT_VCARD_SELECTING 0x0010 +#define BTA_PBAP_FEAT_ENHANCED_MISSED_CALLS 0x0020 +#define BTA_PBAP_FEAT_X_BT_UCI_VCARD_PROPERTY 0x0040 +#define BTA_PBAP_FEAT_X_BT_UID_VCARD_PROPERTY 0x0080 +#define BTA_PBAP_FEAT_CONTACT_REFERENCING 0x0100 +#define BTA_PBAP_FEAT_DEFAULT_CONTACT_IMAGE_FORMAT 0x0200 + +/* PBAP default supported features */ +#define BTA_PBAP_DEFAULT_SUPPORTED_FEATURES 0x0003 + +/* Application parameters tag id */ +#define BTA_PBAP_APP_PARAM_ORDER 0x01 +#define BTA_PBAP_APP_PARAM_SEARCH_VALUE 0x02 +#define BTA_PBAP_APP_PARAM_SEARCH_PROPERTY 0x03 +#define BTA_PBAP_APP_PARAM_MAX_LIST_COUNT 0x04 +#define BTA_PBAP_APP_PARAM_LIST_START_OFFSET 0x05 +#define BTA_PBAP_APP_PARAM_PROPERTY_SELECTOR 0x06 +#define BTA_PBAP_APP_PARAM_FORMAT 0x07 +#define BTA_PBAP_APP_PARAM_PHONE_BOOK_SIZE 0x08 +#define BTA_PBAP_APP_PARAM_NEW_MISSED_CALLS 0x09 +#define BTA_PBAP_APP_PARAM_PRIMARY_FOLDER_VERSION 0x0A +#define BTA_PBAP_APP_PARAM_SECONDARY_FOLDER_VERSION 0x0B +#define BTA_PBAP_APP_PARAM_VCARD_SELECTOR 0x0C +#define BTA_PBAP_APP_PARAM_DATABASE_IDENTIFIER 0x0D +#define BTA_PBAP_APP_PARAM_VCARD_SELECTOR_OPERATOR 0x0E +#define BTA_PBAP_APP_PARAM_RESET_NEW_MISSED_CALLS 0x0F +#define BTA_PBAP_APP_PARAM_PBAP_SUPPORTED_FEATURES 0x10 + +/* Application parameters length (except SearchValue) */ +#define BTA_PBAP_APP_PARAM_LENGTH_ORDER 1 +#define BTA_PBAP_APP_PARAM_LENGTH_SEARCH_PROPERTY 1 +#define BTA_PBAP_APP_PARAM_LENGTH_MAX_LIST_COUNT 2 +#define BTA_PBAP_APP_PARAM_LENGTH_LIST_START_OFFSET 2 +#define BTA_PBAP_APP_PARAM_LENGTH_PROPERTY_SELECTOR 8 +#define BTA_PBAP_APP_PARAM_LENGTH_FORMAT 1 +#define BTA_PBAP_APP_PARAM_LENGTH_PHONE_BOOK_SIZE 2 +#define BTA_PBAP_APP_PARAM_LENGTH_NEW_MISSED_CALLS 1 +#define BTA_PBAP_APP_PARAM_LENGTH_PRIMARY_FOLDER_VERSION 16 +#define BTA_PBAP_APP_PARAM_LENGTH_SECONDARY_FOLDER_VERSION 16 +#define BTA_PBAP_APP_PARAM_LENGTH_VCARD_SELECTOR 8 +#define BTA_PBAP_APP_PARAM_LENGTH_DATABASE_IDENTIFIER 16 +#define BTA_PBAP_APP_PARAM_LENGTH_VCARD_SELECTOR_OPERATOR 1 +#define BTA_PBAP_APP_PARAM_LENGTH_RESET_NEW_MISSED_CALLS 1 +#define BTA_PBAP_APP_PARAM_LENGTH_PBAP_SUPPORTED_FEATURES 4 + +/* Application parameter tag (1 byte) + Application parameter length (1 byte) */ +#define BTA_PBAP_APP_PARAM_HEADER_LENGTH 2 + +/* The minimal buff size that can hold all pull phone book application parameters */ +#define BTA_PBAP_PULL_PHONE_BOOK_APP_PARAM_BUFF_SIZE_MIN 37 + +/* The minimal buff size that can hold pull vCard listing application parameters, except the value of SearchValue */ +#define BTA_PBAP_PULL_VCARD_LISTING_APP_PARAM_BUFF_SIZE_MIN 34 + +/* The minimal buff size that can hold all pull vCard entry application parameters */ +#define BTA_PBAP_PULL_VCARD_ENTRY_APP_PARAM_BUFF_SIZE_MIN 13 + +/* Application parameters bit mask */ +#define PBAP_APP_PARAM_BIT_MASK_ORDER 0x0001 +#define PBAP_APP_PARAM_BIT_MASK_SEARCH_VALUE 0x0002 +#define PBAP_APP_PARAM_BIT_MASK_SEARCH_PROPERTY 0x0004 +#define PBAP_APP_PARAM_BIT_MASK_MAX_LIST_COUNT 0x0008 +#define PBAP_APP_PARAM_BIT_MASK_LIST_START_OFFSET 0x0010 +#define PBAP_APP_PARAM_BIT_MASK_PROPERTY_SELECTOR 0x0020 +#define PBAP_APP_PARAM_BIT_MASK_FORMAT 0x0040 +#define PBAP_APP_PARAM_BIT_MASK_PHONE_BOOK_SIZE 0x0080 +#define PBAP_APP_PARAM_BIT_MASK_NEW_MISSED_CALLS 0x0100 +#define PBAP_APP_PARAM_BIT_MASK_PRIMARY_FOLDER_VERSION 0x0200 +#define PBAP_APP_PARAM_BIT_MASK_SECONDARY_FOLDER_VERSION 0x0400 +#define PBAP_APP_PARAM_BIT_MASK_VCARD_SELECTOR 0x0800 +#define PBAP_APP_PARAM_BIT_MASK_DATABASE_IDENTIFIER 0x1000 +#define PBAP_APP_PARAM_BIT_MASK_VCARD_SELECTOR_OPERATOR 0x2000 +#define PBAP_APP_PARAM_BIT_MASK_RESET_NEW_MISSED_CALLS 0x4000 +#define PBAP_APP_PARAM_BIT_MASK_PBAP_SUPPORTED_FEATURES 0x8000 diff --git a/components/bt/host/bluedroid/bta/pba/bta_pba_client_act.c b/components/bt/host/bluedroid/bta/pba/bta_pba_client_act.c new file mode 100644 index 000000000000..8c69e6244a64 --- /dev/null +++ b/components/bt/host/bluedroid/bta/pba/bta_pba_client_act.c @@ -0,0 +1,702 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include "osi/allocator.h" +#include "common/bt_target.h" +#include "stack/bt_types.h" +#include "stack/obex_api.h" +#include "stack/goep_common.h" +#include "stack/goepc_api.h" +#include "bta_pba_client_int.h" + +#if BTA_PBA_CLIENT_INCLUDED + +static const UINT8 pbap_target_uuid[16] = {0x79, 0x61, 0x35, 0xf0, 0xf0, 0xc5, 0x11, 0xd8, 0x09, 0x66, 0x08, 0x00, 0x20, 0x0c, 0x9a, 0x66}; +static const char *type_pull_phone_book = "x-bt/phonebook"; +static const char *type_pull_vcard_listing = "x-bt/vcard-listing"; +static const char *type_pull_vcard_entry = "x-bt/vcard"; + +#define TYPE_LEN_PULL_PHONE_BOOK 15 +#define TYPE_LEN_PULL_VCARD_LISTING 19 +#define TYPE_LEN_PULL_VCARD_ENTRY 11 + +static void free_ccb(tBTA_PBA_CLIENT_CCB *p_ccb) +{ + /* free sdp db */ + bta_pba_client_free_db(p_ccb); + + /* clear all field, set allocated to 0 */ + memset(p_ccb, 0, sizeof(tBTA_PBA_CLIENT_CCB)); +} + +static void close_goepc_and_report(tBTA_PBA_CLIENT_CCB *p_ccb, tBTA_PBA_CLIENT_ERR reason) +{ + /* remove goep connection */ + if (p_ccb->goep_handle != 0) { + GOEPC_Close(p_ccb->goep_handle); + p_ccb->goep_handle = 0; + } + + /* report connection closed event */ + tBTA_PBA_CLIENT_CONN conn; + conn.handle = p_ccb->allocated; + conn.error = reason; + bdcpy(conn.bd_addr, p_ccb->bd_addr); + bta_pba_client_cb.p_cback(BTA_PBA_CLIENT_CONN_CLOSE_EVT, (tBTA_PBA_CLIENT *)&conn); + + /* free ccb */ + free_ccb(p_ccb); +} + +static void build_and_send_empty_get_req(tBTA_PBA_CLIENT_CCB *p_ccb) +{ + tOBEX_PARSE_INFO info = {0}; + info.opcode = OBEX_OPCODE_GET_FINAL; + /* empty get request, try to use a smaller buff size */ + UINT16 tx_buff_size = BT_SMALL_BUFFER_SIZE < p_ccb->max_tx ? BT_SMALL_BUFFER_SIZE : p_ccb->max_tx; + UINT16 ret = GOEPC_PrepareRequest(p_ccb->goep_handle, &info, tx_buff_size); + ret |= GOEPC_RequestAddHeader(p_ccb->goep_handle, OBEX_HEADER_ID_CONNECTION_ID, (UINT8 *)(&p_ccb->goep_cid), 4); + ret |= GOEPC_SendRequest(p_ccb->goep_handle); + if (ret != GOEP_SUCCESS) { + close_goepc_and_report(p_ccb, BTA_PBA_CLIENT_GOEP_ERROR); + } +} + +static uint8_t get_operation_response_event(tBTA_PBA_CLIENT_OP operation) +{ + uint8_t event = 0; + switch (operation) + { + case BTA_PBA_CLIENT_OP_PULL_PHONE_BOOK: + event = BTA_PBA_CLIENT_PULL_PHONE_BOOK_RSP_EVT; + break; + case BTA_PBA_CLIENT_OP_SET_PHONE_BOOK: + event = BTA_PBA_CLIENT_SET_PHONE_BOOK_RSP_EVT; + break; + case BTA_PBA_CLIENT_OP_PULL_VCARD_LISTING: + event = BTA_PBA_CLIENT_PULL_VCARD_LISTING_RSP_EVT; + break; + case BTA_PBA_CLIENT_OP_PULL_VCARD_ENTRY: + event = BTA_PBA_CLIENT_PULL_VCARD_ENTRY_RSP_EVT; + break; + default: + assert(0); + break; + } + return event; +} + +static tBTA_PBA_CLIENT_ERR calculate_response_error(uint8_t response_code) +{ + tBTA_PBA_CLIENT_ERR error = BTA_PBA_CLIENT_FAIL; + /* always treat error response code as final response */ + response_code |= OBEX_FINAL_BIT_MASK; + switch (response_code) + { + case (OBEX_RESPONSE_CODE_BAD_REQUEST | OBEX_FINAL_BIT_MASK): + error = BTA_PBA_CLIENT_BAD_REQUEST; + break; + case (OBEX_RESPONSE_CODE_UNAUTHORIZED | OBEX_FINAL_BIT_MASK): + error = BTA_PBA_CLIENT_UNAUTHORIZED; + break; + case (OBEX_RESPONSE_CODE_FORBIDDEN | OBEX_FINAL_BIT_MASK): + error = BTA_PBA_CLIENT_FORBIDDEN; + break; + case (OBEX_RESPONSE_CODE_NOT_FOUND | OBEX_FINAL_BIT_MASK): + error = BTA_PBA_CLIENT_NOT_FOUND; + break; + case (OBEX_RESPONSE_CODE_NOT_ACCEPTABLE | OBEX_FINAL_BIT_MASK): + error = BTA_PBA_CLIENT_NOT_ACCEPTABLE; + break; + case (OBEX_RESPONSE_CODE_PRECONDITION_FAILED | OBEX_FINAL_BIT_MASK): + error = BTA_PBA_CLIENT_PRECONDITION_FAILED; + break; + case (OBEX_RESPONSE_CODE_NOT_IMPLEMENTED | OBEX_FINAL_BIT_MASK): + error = BTA_PBA_CLIENT_NOT_IMPLEMENTED; + break; + case (OBEX_RESPONSE_CODE_SERVICE_UNAVAILABLE | OBEX_FINAL_BIT_MASK): + error = BTA_PBA_CLIENT_SERVICE_UNAVAILABLE; + break; + default: + break; + } + return error; +} + +static void report_data_event(tBTA_PBA_CLIENT_CCB *p_ccb, UINT8 *data, UINT16 data_len, UINT8 *app_param, + UINT16 app_param_len, BOOLEAN final, BT_HDR *pkt) +{ + uint8_t event = get_operation_response_event(p_ccb->operation); + tBTA_PBA_CLIENT_RESPONSE response; + response.status = BTA_PBA_CLIENT_NO_ERROR; + response.handle = p_ccb->allocated; + response.final = final; + response.data_len = data_len; + response.data = data; + response.app_param_len = app_param_len; + response.app_param = app_param; + response.pkt = pkt; + bta_pba_client_cb.p_cback(event, (tBTA_PBA_CLIENT *)&response); +} + +static void report_error_data_event(tBTA_PBA_CLIENT_CCB *p_ccb, tBTA_PBA_CLIENT_ERR status) +{ + uint8_t event = get_operation_response_event(p_ccb->operation); + tBTA_PBA_CLIENT_RESPONSE response; + memset(&response, 0, sizeof(tBTA_PBA_CLIENT_RESPONSE)); + response.status = status; + response.handle = p_ccb->allocated; + response.final = TRUE; + bta_pba_client_cb.p_cback(event, (tBTA_PBA_CLIENT *)&response); +} + +static void ascii_to_utf16(const UINT8 *src, UINT16 len, UINT8 *dst) +{ + UINT16 pos = 0; + for (int i = 0 ; i < len ; i++){ + dst[pos++] = 0; + dst[pos++] = src[i]; + } +} + +void bta_pba_client_api_enable(tBTA_PBA_CLIENT_DATA *p_data) +{ + /* initialize control block */ + memset(&bta_pba_client_cb, 0, sizeof(tBTA_PBA_CLIENT_CB)); + + /* store callback function */ + bta_pba_client_cb.p_cback = p_data->api_enable.p_cback; + + bta_pba_client_cb.p_cback(BTA_PBA_CLIENT_ENABLE_EVT, NULL); +} + +void bta_pba_client_api_disable(tBTA_PBA_CLIENT_DATA *p_data) +{ + if (!bta_sys_is_register(BTA_ID_PBC)) { + return; + } + /* deregister with BTA system manager */ + bta_sys_deregister(BTA_ID_PBC); + + /* close all connections */ + for (int i = 0; i < PBA_CLIENT_MAX_CONNECTION; ++i) { + if (bta_pba_client_cb.ccb[i].allocated) { + close_goepc_and_report(&bta_pba_client_cb.ccb[i], BTA_PBA_CLIENT_DISABLE); + } + } + + /* store and clear callback function */ + tBTA_PBA_CLIENT_CBACK *p_cback = bta_pba_client_cb.p_cback; + bta_pba_client_cb.p_cback = NULL; + + if(p_cback) { + p_cback(BTA_PBA_CLIENT_DISABLE_EVT, NULL); + } +} + +void bta_pba_client_api_register(tBTA_PBA_CLIENT_DATA *p_data) +{ + /* create SDP records */ + bta_pba_client_create_record(p_data->api_register.name); + + bta_pba_client_cb.p_cback(BTA_PBA_CLIENT_REGISTER_EVT, NULL); +} + +void bta_pba_client_api_deregister(tBTA_PBA_CLIENT_DATA *p_data) +{ + /* delete SDP records */ + bta_pba_client_del_record(); + + bta_pba_client_cb.p_cback(BTA_PBA_CLIENT_DEREGISTER_EVT, NULL); +} + +void bta_pba_client_api_open(tBTA_PBA_CLIENT_CCB *p_ccb, tBTA_PBA_CLIENT_DATA *p_data) +{ + p_ccb->sec_mask = p_data->api_open.sec_mask; + if (p_data->api_open.mtu < PBA_CLIENT_MIN_MTU || p_data->api_open.mtu > PBA_CLIENT_MAX_MTU) { + p_ccb->max_rx = PBA_CLIENT_MAX_MTU; + p_ccb->max_tx = PBA_CLIENT_MAX_MTU; + } + else { + p_ccb->max_rx = p_data->api_open.mtu; + p_ccb->max_tx = p_data->api_open.mtu; + } + bdcpy(p_ccb->bd_addr, p_data->api_open.bd_addr); + p_ccb->our_supported_feat = p_data->api_open.supported_feat; + if (!bta_pba_client_do_disc(p_ccb)) { + close_goepc_and_report(p_ccb, BTA_PBA_CLIENT_SDP_ERROR); + } +} + +void bta_pba_client_api_close(tBTA_PBA_CLIENT_CCB *p_ccb, tBTA_PBA_CLIENT_DATA *p_data) +{ + UNUSED(p_data); + tOBEX_PARSE_INFO info = {0}; + + info.opcode = OBEX_OPCODE_DISCONNECT; + UINT16 tx_buff_size = BT_SMALL_BUFFER_SIZE < p_ccb->max_tx ? BT_SMALL_BUFFER_SIZE : p_ccb->max_tx; + UINT16 ret = GOEPC_PrepareRequest(p_ccb->goep_handle, &info, tx_buff_size); + ret |= GOEPC_RequestAddHeader(p_ccb->goep_handle, OBEX_HEADER_ID_CONNECTION_ID, (UINT8 *)(&p_ccb->goep_cid), 4); + ret |= GOEPC_SendRequest(p_ccb->goep_handle); + + if (ret != GOEP_SUCCESS) { + /* anyway, this close operation is requested by upper, set reason to success */ + close_goepc_and_report(p_ccb, BTA_PBA_CLIENT_NO_ERROR); + } +} + +void bta_pba_client_api_req(tBTA_PBA_CLIENT_CCB *p_ccb, tBTA_PBA_CLIENT_DATA *p_data) +{ + tOBEX_PARSE_INFO info = {0}; + UINT16 ret; + + if (p_data->api_req.operation == BTA_PBA_CLIENT_OP_SET_PHONE_BOOK) { + info.opcode = OBEX_OPCODE_SETPATH; + info.flags = p_data->api_req.flags; + } + else { + info.opcode = OBEX_OPCODE_GET_FINAL; + } + ret = GOEPC_PrepareRequest(p_ccb->goep_handle, &info, p_ccb->max_tx); + if (ret != GOEP_SUCCESS) { + goto error; + } + + ret |= GOEPC_RequestAddHeader(p_ccb->goep_handle, OBEX_HEADER_ID_CONNECTION_ID, (UINT8 *)(&p_ccb->goep_cid), 4); + + if (p_data->api_req.operation != BTA_PBA_CLIENT_OP_SET_PHONE_BOOK) { + ret |= GOEPC_RequestSetSRM(p_ccb->goep_handle, TRUE, FALSE); + } + + if (p_data->api_req.name) { + UINT16 name_len = strlen(p_data->api_req.name) + 1; + if (name_len == 1) { + /* empty string, add empty name header */ + ret |= GOEPC_RequestAddHeader(p_ccb->goep_handle, OBEX_HEADER_ID_NAME, NULL, 0); + } + else { + UINT8 *utf16_name = osi_malloc(2 * name_len); + assert(utf16_name != NULL); + ascii_to_utf16((UINT8 *)p_data->api_req.name, name_len, utf16_name); + ret |= GOEPC_RequestAddHeader(p_ccb->goep_handle, OBEX_HEADER_ID_NAME, utf16_name, 2 * name_len); + osi_free(utf16_name); + } + osi_free(p_data->api_req.name); + p_data->api_req.name = NULL; + } + + switch (p_data->api_req.operation) + { + case BTA_PBA_CLIENT_OP_PULL_PHONE_BOOK: + ret |= GOEPC_RequestAddHeader(p_ccb->goep_handle, OBEX_HEADER_ID_TYPE, (const UINT8 *)type_pull_phone_book, TYPE_LEN_PULL_PHONE_BOOK); + break; + case BTA_PBA_CLIENT_OP_PULL_VCARD_LISTING: + ret |= GOEPC_RequestAddHeader(p_ccb->goep_handle, OBEX_HEADER_ID_TYPE, (const UINT8 *)type_pull_vcard_listing, TYPE_LEN_PULL_VCARD_LISTING); + break; + case BTA_PBA_CLIENT_OP_PULL_VCARD_ENTRY: + ret |= GOEPC_RequestAddHeader(p_ccb->goep_handle, OBEX_HEADER_ID_TYPE, (const UINT8 *)type_pull_vcard_entry, TYPE_LEN_PULL_VCARD_ENTRY); + break; + default: + break; + } + + if (p_data->api_req.app_param) { + ret |= GOEPC_RequestAddHeader(p_ccb->goep_handle, OBEX_HEADER_ID_APP_PARAM, p_data->api_req.app_param, p_data->api_req.app_param_len); + osi_free(p_data->api_req.app_param); + } + + ret |= GOEPC_SendRequest(p_ccb->goep_handle); + p_ccb->operation = p_data->api_req.operation; + if (ret != GOEP_SUCCESS) { + goto error; + } + return; + +error: + if (p_data->api_req.name) { + osi_free(p_data->api_req.name); + } + if (p_data->api_req.app_param) { + osi_free(p_data->api_req.app_param); + } + close_goepc_and_report(p_ccb, BTA_PBA_CLIENT_GOEP_ERROR); +} + +static void goep_event_callback(UINT16 handle, UINT8 event, tGOEPC_MSG *p_msg) +{ + tBTA_PBA_CLIENT_DATA *p_data = NULL; + + switch (event) + { + case GOEPC_OPENED_EVT: + p_data = (tBTA_PBA_CLIENT_DATA *)osi_malloc(sizeof(tBTA_PBA_CLIENT_GOEP_CONNECT)); + assert(p_data != NULL); + p_data->goep_connect.hdr.event = BTA_PBA_CLIENT_GOEP_CONNECT_EVT; + p_data->goep_connect.hdr.layer_specific = handle; + p_data->goep_connect.our_mtu = p_msg->opened.our_mtu; + p_data->goep_connect.peer_mtu = p_msg->opened.peer_mtu; + break; + case GOEPC_CLOSED_EVT: + p_data = (tBTA_PBA_CLIENT_DATA *)osi_malloc(sizeof(tBTA_PBA_CLIENT_GOEP_DISCONNECT)); + assert(p_data != NULL); + p_data->goep_disconnect.hdr.event = BTA_PBA_CLIENT_GOEP_DISCONNECT_EVT; + p_data->goep_disconnect.hdr.layer_specific = handle; + p_data->goep_disconnect.reason = p_msg->closed.reason; + break; + case GOEPC_RESPONSE_EVT: + p_data = (tBTA_PBA_CLIENT_DATA *)osi_malloc(sizeof(tBTA_PBA_CLIENT_GOEP_RESPONSE)); + assert(p_data != NULL); + p_data->goep_response.hdr.layer_specific = handle; + p_data->goep_response.pkt = p_msg->response.pkt; + p_data->goep_response.opcode = p_msg->response.opcode; + p_data->goep_response.srm_en = p_msg->response.srm_en; + p_data->goep_response.srm_wait = p_msg->response.srm_wait; + if (p_msg->response.final) { + p_data->goep_response.hdr.event = BTA_PBA_CLIENT_RESPONSE_FINAL_EVT; + } + else { + p_data->hdr.event = BTA_PBA_CLIENT_RESPONSE_EVT; + } + break; + case GOEPC_MTU_CHANGED_EVT: + case GOEPC_CONGEST_EVT: + case GOEPC_UNCONGEST_EVT: + /* ignore these event */ + break; + default: + APPL_TRACE_WARNING("%s, unknown goep event: %d", __FUNCTION__, event); + break; + } + if (p_data != NULL) { + bta_sys_sendmsg(p_data); + } +} + +void bta_pba_client_do_connect(tBTA_PBA_CLIENT_CCB *p_ccb, tBTA_PBA_CLIENT_DATA *p_data) +{ + tBTA_PBA_CLIENT_ERR reason = BTA_PBA_CLIENT_FAIL; + + /* find attr in sdp discovery result */ + BOOLEAN found = bta_pba_client_sdp_find_attr(p_ccb); + if (found) { + tOBEX_SVR_INFO svr = {0}; + if (p_ccb->peer_l2cap_psm != 0) { + /* peer support obex over l2cap, use it */ + svr.tl = OBEX_OVER_L2CAP; + svr.l2cap.psm = p_ccb->peer_l2cap_psm; + svr.l2cap.sec_mask = p_ccb->sec_mask; + svr.l2cap.pref_mtu = 0; + bdcpy(svr.l2cap.addr, p_ccb->bd_addr); + } + else { + /* otherwise, use obex over rfcomm */ + svr.tl = OBEX_OVER_RFCOMM; + svr.rfcomm.scn = p_ccb->peer_rfcomm_scn; + svr.rfcomm.sec_mask = p_ccb->sec_mask; + svr.rfcomm.pref_mtu = 0; + bdcpy(svr.rfcomm.addr, p_ccb->bd_addr); + } + + if (GOEPC_Open(&svr, goep_event_callback, &p_ccb->goep_handle) == GOEP_SUCCESS) { + /* start connection success */ + return; + } + else { + reason = BTA_PBA_CLIENT_GOEP_ERROR; + } + } + else { + reason = BTA_PBA_CLIENT_SDP_ERROR; + } + + /* critical sdp attribute not found or start goep connection failed */ + tBTA_PBA_CLIENT_CONN conn; + conn.handle = 0; + conn.error = reason; + bdcpy(conn.bd_addr, p_ccb->bd_addr); + bta_pba_client_cb.p_cback(BTA_PBA_CLIENT_CONN_CLOSE_EVT, (tBTA_PBA_CLIENT *)&conn); + + /* free ccb */ + free_ccb(p_ccb); +} + +void bta_pba_client_authenticate(tBTA_PBA_CLIENT_CCB *p_ccb, tBTA_PBA_CLIENT_DATA *p_data) +{ + /* [todo]: support authenticate */ +} + +void bta_pba_client_connect(tBTA_PBA_CLIENT_CCB *p_ccb, tBTA_PBA_CLIENT_DATA *p_data) +{ + tBTA_PBA_CLIENT_CONN conn; + conn.handle = p_ccb->allocated; + conn.peer_supported_repo = p_ccb->peer_supported_repo; + conn.peer_supported_feat = p_ccb->peer_supported_feat; + conn.error = BTA_PBA_CLIENT_NO_ERROR; + bdcpy(conn.bd_addr, p_ccb->bd_addr); + bta_pba_client_cb.p_cback(BTA_PBA_CLIENT_CONN_OPEN_EVT, (tBTA_PBA_CLIENT *)&conn); +} + +void bta_pba_client_force_disconnect(tBTA_PBA_CLIENT_CCB *p_ccb, tBTA_PBA_CLIENT_DATA *p_data) +{ + UNUSED(p_data); + + /* force disconnect is requested by upper, set reason to success */ + close_goepc_and_report(p_ccb, BTA_PBA_CLIENT_NO_ERROR); +} + +void bta_pba_client_response(tBTA_PBA_CLIENT_CCB *p_ccb, tBTA_PBA_CLIENT_DATA *p_data) +{ + tOBEX_PARSE_INFO info; + tBTA_PBA_CLIENT_ERR reason = BTA_PBA_CLIENT_GOEP_ERROR; + + OBEX_ParseResponse(p_data->goep_response.pkt, p_data->goep_response.opcode, &info); + if (p_data->goep_response.opcode == OBEX_OPCODE_GET_FINAL && + (info.response_code == OBEX_RESPONSE_CODE_CONTINUE || info.response_code == (OBEX_RESPONSE_CODE_CONTINUE | OBEX_FINAL_BIT_MASK))) { + UINT8 *header = NULL; + UINT8 *body_data = NULL; + UINT16 body_data_len = 0; + UINT8 *app_param = NULL; + UINT16 app_param_len = 0; + while((header = OBEX_GetNextHeader(p_data->goep_response.pkt, &info)) != NULL) { + switch (*header) + { + case OBEX_HEADER_ID_BODY: + case OBEX_HEADER_ID_END_OF_BODY: + if (body_data == NULL) { + /* first body header */ + body_data = header + 3; /* skip opcode, length */ + body_data_len = OBEX_GetHeaderLength(header) - 3; + } + else { + /* another body header found */ + report_data_event(p_ccb, body_data, body_data_len, NULL, 0, FALSE, NULL); + body_data = header + 3; /* skip opcode, length */ + body_data_len = OBEX_GetHeaderLength(header) - 3; + } + break; + case OBEX_HEADER_ID_APP_PARAM: + app_param = header + 3; + app_param_len = OBEX_GetHeaderLength(header) - 3; + break; + default: + break; + } + } + if (body_data != NULL || app_param != NULL) { + /* report body data and app param, dont free packet here */ + report_data_event(p_ccb, body_data, body_data_len, app_param, app_param_len, FALSE, p_data->goep_response.pkt); + } + else { + /* not any body data or app param */ + osi_free(p_data->goep_response.pkt); + } + + /* if SRM not enable, we need to send a empty get request */ + if (!p_data->goep_response.srm_en || p_data->goep_response.srm_wait) { + build_and_send_empty_get_req(p_ccb); + } + } + else { + /* unexpected opcode or response code */ + reason = calculate_response_error(info.response_code); + goto error; + } + return; + +error: + if (p_data->goep_response.pkt != NULL) { + osi_free(p_data->goep_response.pkt); + } + close_goepc_and_report(p_ccb, reason); +} + +void bta_pba_client_response_final(tBTA_PBA_CLIENT_CCB *p_ccb, tBTA_PBA_CLIENT_DATA *p_data) +{ + tOBEX_PARSE_INFO info; + UINT8 *header = NULL; + tBTA_PBA_CLIENT_ERR reason = BTA_PBA_CLIENT_FAIL; + + OBEX_ParseResponse(p_data->goep_response.pkt, p_data->goep_response.opcode, &info); + if (p_data->goep_response.opcode == OBEX_OPCODE_CONNECT) { + if (info.response_code == (OBEX_RESPONSE_CODE_OK | OBEX_FINAL_BIT_MASK)) { + /* obex connect success */ + if (info.max_packet_length < 255) { + p_ccb->max_tx = 255; + } + else if (p_ccb->max_tx > info.max_packet_length) { + p_ccb->max_tx = info.max_packet_length; + } + BOOLEAN cid_found = false; + while((header = OBEX_GetNextHeader(p_data->goep_response.pkt, &info)) != NULL) { + if (*header == OBEX_HEADER_ID_CONNECTION_ID) { + cid_found = true; + memcpy((UINT8 *)(&p_ccb->goep_cid), header + 1, 4); + break; + } + } + if (!cid_found) { + goto error; + } + + BT_HDR *p_buf = (BT_HDR *)osi_malloc(sizeof(BT_HDR)); + assert(p_buf != NULL); + p_buf->event = BTA_PBA_CLIENT_CONNECT_EVT; + p_buf->layer_specific = p_ccb->allocated; + bta_sys_sendmsg(p_buf); + } + else if (info.response_code == (OBEX_RESPONSE_CODE_UNAUTHORIZED | OBEX_FINAL_BIT_MASK)){ + /* need to authenticate */ + if (p_ccb->authenticate) { + /* already try to authenticate, but failed */ + reason = BTA_PBA_CLIENT_AUTH_FAIL; + goto error; + } + + p_ccb->authenticate = TRUE; + /* [todo]: we don't support authenticate currently */ + goto error; + } + else { + /* other unexpected response code */ + goto error; + } + osi_free(p_data->goep_response.pkt); + } + else if (p_data->goep_response.opcode == OBEX_OPCODE_GET_FINAL) { + /* check response code is success */ + if (info.response_code == (OBEX_RESPONSE_CODE_OK | OBEX_FINAL_BIT_MASK)) { + UINT8 *body_data = NULL; + UINT16 body_data_len = 0; + UINT8 *app_param = NULL; + UINT16 app_param_len = 0; + while((header = OBEX_GetNextHeader(p_data->goep_response.pkt, &info)) != NULL) { + switch (*header) + { + /* actually, BODY should not in this final response */ + case OBEX_HEADER_ID_BODY: + case OBEX_HEADER_ID_END_OF_BODY: + if (body_data == NULL) { + /* first body header */ + body_data = header + 3; /* skip opcode, length */ + body_data_len = OBEX_GetHeaderLength(header) - 3; + } + else { + /* another body header found */ + report_data_event(p_ccb, body_data, body_data_len, NULL, 0, FALSE, NULL); + body_data = header + 3; /* skip opcode, length */ + body_data_len = OBEX_GetHeaderLength(header) - 3; + } + break; + case OBEX_HEADER_ID_APP_PARAM: + app_param = header + 3; + app_param_len = OBEX_GetHeaderLength(header) - 3; + break; + default: + break; + } + } + if (body_data != NULL || app_param != NULL) { + /* report body data and app param, dont free packet here */ + report_data_event(p_ccb, body_data, body_data_len, app_param, app_param_len, TRUE, p_data->goep_response.pkt); + /* done, return */ + return; + } + } + /* unexpected response code or body data not found */ + reason = calculate_response_error(info.response_code); + report_error_data_event(p_ccb, reason); + osi_free(p_data->goep_response.pkt); + + /* state machine is good, don't goto error */ + } + else if (p_data->goep_response.opcode == OBEX_OPCODE_SETPATH) { + if (info.response_code == (OBEX_RESPONSE_CODE_OK | OBEX_FINAL_BIT_MASK)) { + report_data_event(p_ccb, NULL, 0, NULL, 0, TRUE, NULL); + } + else { + reason = calculate_response_error(info.response_code); + report_error_data_event(p_ccb, reason); + } + osi_free(p_data->goep_response.pkt); + } + else if (p_data->goep_response.opcode == OBEX_OPCODE_DISCONNECT) { + /* received disconnect response, close goep connection now */ + reason = BTA_PBA_CLIENT_NO_ERROR; + close_goepc_and_report(p_ccb, reason); + osi_free(p_data->goep_response.pkt); + } + else { + /* unexpected opcode or response code */ + reason = calculate_response_error(info.response_code); + goto error; + } + return; + +error: + if (p_data->goep_response.pkt != NULL) { + osi_free(p_data->goep_response.pkt); + } + close_goepc_and_report(p_ccb, reason); +} + +void bta_pba_client_goep_connect(tBTA_PBA_CLIENT_CCB *p_ccb, tBTA_PBA_CLIENT_DATA *p_data) +{ + /* limit max rx to our goep mtu */ + if (p_ccb->max_rx > p_data->goep_connect.our_mtu) { + p_ccb->max_rx = p_data->goep_connect.our_mtu; + } + /* limit max tx to peer goep mtu */ + if (p_ccb->max_tx > p_data->goep_connect.peer_mtu) { + p_ccb->max_tx = p_data->goep_connect.peer_mtu; + } + + /* build and send obex connect request */ + tOBEX_PARSE_INFO info = {0}; + info.opcode = OBEX_OPCODE_CONNECT; + info.obex_version_number = OBEX_VERSION_NUMBER; + info.max_packet_length = p_ccb->max_rx; + /* before obex connect response, we dont know the real max_tx, use BT_SMALL_BUFFER_SIZE as tx buff size */ + UINT16 ret = GOEPC_PrepareRequest(p_ccb->goep_handle, &info, BT_SMALL_BUFFER_SIZE); + /* add target header */ + ret |= GOEPC_RequestAddHeader(p_ccb->goep_handle, OBEX_HEADER_ID_TARGET, pbap_target_uuid, 16); + if (p_ccb->send_supported_feat) { + /* add application parameters with supported features */ + UINT8 app_param[6]; + app_param[0] = BTA_PBAP_APP_PARAM_PBAP_SUPPORTED_FEATURES; + app_param[1] = BTA_PBAP_APP_PARAM_LENGTH_PBAP_SUPPORTED_FEATURES; + UINT32_TO_FIELD(&app_param[2], p_ccb->our_supported_feat); + ret |= GOEPC_RequestAddHeader(p_ccb->goep_handle, OBEX_HEADER_ID_APP_PARAM, app_param, 6); + } + ret |= GOEPC_SendRequest(p_ccb->goep_handle); + if (ret != GOEP_SUCCESS) { + close_goepc_and_report(p_ccb, BTA_PBA_CLIENT_GOEP_ERROR); + } +} + +void bta_pba_client_goep_disconnect(tBTA_PBA_CLIENT_CCB *p_ccb, tBTA_PBA_CLIENT_DATA *p_data) +{ + p_ccb->goep_handle = 0; + + /* report connection closed event */ + tBTA_PBA_CLIENT_CONN conn; + conn.handle = p_ccb->allocated; + bdcpy(conn.bd_addr, p_ccb->bd_addr); + conn.error = BTA_PBA_CLIENT_GOEP_ERROR; + bta_pba_client_cb.p_cback(BTA_PBA_CLIENT_CONN_CLOSE_EVT, (tBTA_PBA_CLIENT *)&conn); + free_ccb(p_ccb); +} + +void bta_pba_client_free_response(tBTA_PBA_CLIENT_CCB *p_ccb, tBTA_PBA_CLIENT_DATA *p_data) +{ + if (p_data->goep_response.pkt != NULL) { + osi_free(p_data->goep_response.pkt); + } + close_goepc_and_report(p_ccb, BTA_PBA_CLIENT_GOEP_ERROR); +} + +#endif diff --git a/components/bt/host/bluedroid/bta/pba/bta_pba_client_api.c b/components/bt/host/bluedroid/bta/pba/bta_pba_client_api.c new file mode 100644 index 000000000000..4670c28cf16a --- /dev/null +++ b/components/bt/host/bluedroid/bta/pba/bta_pba_client_api.c @@ -0,0 +1,161 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#include "osi/allocator.h" +#include "common/bt_target.h" +#include "stack/obex_api.h" +#include "stack/goep_common.h" +#include "stack/goepc_api.h" +#include "bta/bta_sys.h" +#include "bta/bta_api.h" +#include "bta_pba_client_int.h" + +#if BTA_PBA_CLIENT_INCLUDED + +static const tBTA_SYS_REG bta_pba_client_reg = { + bta_pba_client_hdl_event, + BTA_PbaClientDisable +}; + +void BTA_PbaClientEnable(tBTA_PBA_CLIENT_CBACK *p_cback) +{ + tBTA_PBA_CLIENT_API_ENABLE *p_buf; + + if (bta_sys_is_register(BTA_ID_PBC)) { + APPL_TRACE_ERROR("BTA PBA Client already enabled"); + return; + } + + /* register with BTA system manager */ + bta_sys_register(BTA_ID_PBC, &bta_pba_client_reg); + + if ((p_buf = (tBTA_PBA_CLIENT_API_ENABLE *)osi_malloc(sizeof(tBTA_PBA_CLIENT_API_ENABLE))) != NULL) { + p_buf->hdr.event = BTA_PBA_CLIENT_API_ENABLE_EVT; + p_buf->p_cback = p_cback; + bta_sys_sendmsg(p_buf); + } +} + +void BTA_PbaClientDisable(void) +{ + BT_HDR *p_buf; + + if ((p_buf = (BT_HDR *) osi_malloc(sizeof(BT_HDR))) != NULL) { + p_buf->event = BTA_PBA_CLIENT_API_DISABLE_EVT; + bta_sys_sendmsg(p_buf); + } +} + +void BTA_PbaClientRegister(const char *server_name) +{ + tBTA_PBA_CLIENT_API_REGISTER *p_buf; + + if ((p_buf = (tBTA_PBA_CLIENT_API_REGISTER *) osi_malloc(sizeof(tBTA_PBA_CLIENT_API_REGISTER))) != NULL) { + p_buf->hdr.event = BTA_PBA_CLIENT_API_REGISTER_EVT; + memcpy(p_buf->name, server_name, strlen(server_name) + 1); + bta_sys_sendmsg(p_buf); + } +} + +void BTA_PbaClientDeregister(void) +{ + BT_HDR *p_buf; + + if ((p_buf = (BT_HDR *) osi_malloc(sizeof(BT_HDR))) != NULL) { + p_buf->event = BTA_PBA_CLIENT_API_DEREGISTER_EVT; + bta_sys_sendmsg(p_buf); + } +} + +void BTA_PbaClientOpen(BD_ADDR bd_addr, tBTA_SEC sec_mask, UINT32 supported_feat, UINT16 mtu) +{ + tBTA_PBA_CLIENT_API_OPEN *p_buf; + + if ((p_buf = (tBTA_PBA_CLIENT_API_OPEN *)osi_malloc(sizeof(tBTA_PBA_CLIENT_API_OPEN))) != NULL) { + p_buf->hdr.event = BTA_PBA_CLIENT_API_OPEN_EVT; + p_buf->sec_mask = sec_mask; + p_buf->mtu = mtu; + bdcpy(p_buf->bd_addr, bd_addr); + bta_sys_sendmsg(p_buf); + } +} + +void BTA_PbaClientClose(UINT16 handle) +{ + BT_HDR *p_buf; + + if ((p_buf = (BT_HDR *) osi_malloc(sizeof(BT_HDR))) != NULL) { + p_buf->event = BTA_PBA_CLIENT_API_CLOSE_EVT; + p_buf->layer_specific = handle; + bta_sys_sendmsg(p_buf); + } +} + +void BTA_PbaClientPullPhoneBook(UINT16 handle, char *name, UINT8 *app_param, UINT16 app_param_len) +{ + tBTA_PBA_CLIENT_API_REQ *p_buf; + + if ((p_buf = (tBTA_PBA_CLIENT_API_REQ *) osi_malloc(sizeof(tBTA_PBA_CLIENT_API_REQ))) != NULL) { + p_buf->hdr.event = BTA_PBA_CLIENT_API_REQ_EVT; + p_buf->hdr.layer_specific = handle; + p_buf->operation = BTA_PBA_CLIENT_OP_PULL_PHONE_BOOK; + p_buf->name = name; + p_buf->app_param = app_param; + p_buf->app_param_len = app_param_len; + bta_sys_sendmsg(p_buf); + } +} + +void BTA_PbaClientSetPhoneBook(UINT16 handle, UINT8 flags, char *name) +{ + tBTA_PBA_CLIENT_API_REQ *p_buf; + + if ((p_buf = (tBTA_PBA_CLIENT_API_REQ *) osi_malloc(sizeof(tBTA_PBA_CLIENT_API_REQ))) != NULL) { + p_buf->hdr.event = BTA_PBA_CLIENT_API_REQ_EVT; + p_buf->hdr.layer_specific = handle; + p_buf->operation = BTA_PBA_CLIENT_OP_SET_PHONE_BOOK; + p_buf->flags = flags; + p_buf->name = name; + p_buf->app_param = NULL; + p_buf->app_param_len = 0; + bta_sys_sendmsg(p_buf); + } +} + +void BTA_PbaClientPullvCardListing(UINT16 handle, char *name, UINT8 *app_param, UINT16 app_param_len) +{ + tBTA_PBA_CLIENT_API_REQ *p_buf; + + if ((p_buf = (tBTA_PBA_CLIENT_API_REQ *) osi_malloc(sizeof(tBTA_PBA_CLIENT_API_REQ))) != NULL) { + p_buf->hdr.event = BTA_PBA_CLIENT_API_REQ_EVT; + p_buf->hdr.layer_specific = handle; + p_buf->operation = BTA_PBA_CLIENT_OP_PULL_VCARD_LISTING; + p_buf->name = name; + p_buf->app_param = app_param; + p_buf->app_param_len = app_param_len; + bta_sys_sendmsg(p_buf); + } +} + +void BTA_PbaClientPullvCardEntry(UINT16 handle, char *name, UINT8 *app_param, UINT16 app_param_len) +{ + tBTA_PBA_CLIENT_API_REQ *p_buf; + + if ((p_buf = (tBTA_PBA_CLIENT_API_REQ *) osi_malloc(sizeof(tBTA_PBA_CLIENT_API_REQ))) != NULL) { + p_buf->hdr.event = BTA_PBA_CLIENT_API_REQ_EVT; + p_buf->hdr.layer_specific = handle; + p_buf->operation = BTA_PBA_CLIENT_OP_PULL_VCARD_ENTRY; + p_buf->name = name; + p_buf->app_param = app_param; + p_buf->app_param_len = app_param_len; + bta_sys_sendmsg(p_buf); + } +} + +#endif diff --git a/components/bt/host/bluedroid/bta/pba/bta_pba_client_main.c b/components/bt/host/bluedroid/bta/pba/bta_pba_client_main.c new file mode 100644 index 000000000000..81c389bb3e06 --- /dev/null +++ b/components/bt/host/bluedroid/bta/pba/bta_pba_client_main.c @@ -0,0 +1,293 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include "osi/allocator.h" +#include "common/bt_target.h" + +#include "stack/obex_api.h" +#include "stack/goep_common.h" +#include "stack/goepc_api.h" +#include "bta_pba_client_int.h" + +#if BTA_PBA_CLIENT_INCLUDED + +/* state machine states */ +enum { + BTA_PBA_CLIENT_INIT_ST, + BTA_PBA_CLIENT_OPENING_ST, + BTA_PBA_CLIENT_OPENED_ST, + BTA_PBA_CLIENT_REQUESTING_ST, + BTA_PBA_CLIENT_CLOSING_ST +}; + +/* state machine action enumeration list */ +enum { + BTA_PBA_CLIENT_API_OPEN, + BTA_PBA_CLIENT_API_CLOSE, + BTA_PBA_CLIENT_API_REQ, + BTA_PBA_CLIENT_DO_CONNECT, + BTA_PBA_CLIENT_AUTHENTICATE, + BTA_PBA_CLIENT_CONNECT, + BTA_PBA_CLIENT_RESPONSE, + BTA_PBA_CLIENT_RESPONSE_FINAL, + BTA_PBA_CLIENT_GOEP_CONNECT, + BTA_PBA_CLIENT_GOEP_DISCONNECT, + BTA_PBA_CLIENT_FORCE_DISCONNECT, + BTA_PBA_CLIENT_FREE_RESPONSE, + BTA_PBA_CLIENT_NUM_ACTIONS +}; + +#define BTA_PBA_CLIENT_IGNORE BTA_PBA_CLIENT_NUM_ACTIONS + +/* type for action functions */ +typedef void (*tBTA_PBA_CLIENT_ACTION)(tBTA_PBA_CLIENT_CCB *p_ccb, tBTA_PBA_CLIENT_DATA *p_data); + +/* action functions table, indexed with action enum */ +const tBTA_PBA_CLIENT_ACTION bta_pba_client_action[] = { + /* BTA_PBA_CLIENT_API_OPEN */ bta_pba_client_api_open, + /* BTA_PBA_CLIENT_API_CLOSE */ bta_pba_client_api_close, + /* BTA_PBA_CLIENT_API_REQ */ bta_pba_client_api_req, + /* BTA_PBA_CLIENT_DO_CONNECT */ bta_pba_client_do_connect, + /* BTA_PBA_CLIENT_AUTHENTICATE */ bta_pba_client_authenticate, + /* BTA_PBA_CLIENT_CONNECT */ bta_pba_client_connect, + /* BTA_PBA_CLIENT_RESPONSE */ bta_pba_client_response, + /* BTA_PBA_CLIENT_RESPONSE_FINAL */ bta_pba_client_response_final, + /* BTA_PBA_CLIENT_GOEP_CONNECT */ bta_pba_client_goep_connect, + /* BTA_PBA_CLIENT_GOEP_DISCONNECT*/ bta_pba_client_goep_disconnect, + /* BTA_PBA_CLIENT_FORCE_DISCONNECT */ bta_pba_client_force_disconnect, + /* BTA_PBA_CLIENT_FREE_RESPONSE */ bta_pba_client_free_response, +}; + +/* state table information */ +#define BTA_PBA_CLIENT_ACTION 0 /* position of action */ +#define BTA_PBA_CLIENT_NEXT_STATE 1 /* position of next state */ +#define BTA_PBA_CLIENT_NUM_COLS 2 /* number of columns */ + +const uint8_t bta_pba_client_st_init[][BTA_PBA_CLIENT_NUM_COLS] = { + /* Event Action Next state */ + /* BTA_PBA_CLIENT_API_OPEN_EVT */ {BTA_PBA_CLIENT_API_OPEN, BTA_PBA_CLIENT_OPENING_ST}, + /* BTA_PBA_CLIENT_API_CLOSE_EVT */ {BTA_PBA_CLIENT_IGNORE, BTA_PBA_CLIENT_INIT_ST}, + /* BTA_PBA_CLIENT_API_REQ_EVT */ {BTA_PBA_CLIENT_IGNORE, BTA_PBA_CLIENT_INIT_ST}, + /* BTA_PBA_CLIENT_DISC_RES_EVT */ {BTA_PBA_CLIENT_IGNORE, BTA_PBA_CLIENT_INIT_ST}, + /* BTA_PBA_CLIENT_AUTHENTICATE_EVT */ {BTA_PBA_CLIENT_IGNORE, BTA_PBA_CLIENT_INIT_ST}, + /* BTA_PBA_CLIENT_CONNECT_EVT */ {BTA_PBA_CLIENT_IGNORE, BTA_PBA_CLIENT_INIT_ST}, + /* BTA_PBA_CLIENT_RESPONSE_EVT */ {BTA_PBA_CLIENT_IGNORE, BTA_PBA_CLIENT_INIT_ST}, + /* BTA_PBA_CLIENT_RESPONSE_FINAL_EVT */ {BTA_PBA_CLIENT_IGNORE, BTA_PBA_CLIENT_INIT_ST}, + /* BTA_PBA_CLIENT_GOEP_CONNECT_EVT */ {BTA_PBA_CLIENT_IGNORE, BTA_PBA_CLIENT_INIT_ST}, + /* BTA_PBA_CLIENT_GOEP_DISCONNECT_EVT */ {BTA_PBA_CLIENT_IGNORE, BTA_PBA_CLIENT_INIT_ST}, +}; + +const uint8_t bta_pba_client_st_opening[][BTA_PBA_CLIENT_NUM_COLS] = { + /* Event Action Next state */ + /* BTA_PBA_CLIENT_API_OPEN_EVT */ {BTA_PBA_CLIENT_IGNORE, BTA_PBA_CLIENT_OPENING_ST}, + /* BTA_PBA_CLIENT_API_CLOSE_EVT */ {BTA_PBA_CLIENT_FORCE_DISCONNECT, BTA_PBA_CLIENT_INIT_ST}, + /* BTA_PBA_CLIENT_API_REQ_EVT */ {BTA_PBA_CLIENT_IGNORE, BTA_PBA_CLIENT_OPENING_ST}, + /* BTA_PBA_CLIENT_DISC_RES_EVT */ {BTA_PBA_CLIENT_DO_CONNECT, BTA_PBA_CLIENT_OPENING_ST}, + /* BTA_PBA_CLIENT_AUTHENTICATE_EVT */ {BTA_PBA_CLIENT_AUTHENTICATE, BTA_PBA_CLIENT_OPENING_ST}, + /* BTA_PBA_CLIENT_CONNECT_EVT */ {BTA_PBA_CLIENT_CONNECT, BTA_PBA_CLIENT_OPENED_ST}, + /* BTA_PBA_CLIENT_RESPONSE_EVT */ {BTA_PBA_CLIENT_FREE_RESPONSE, BTA_PBA_CLIENT_OPENING_ST}, + /* BTA_PBA_CLIENT_RESPONSE_FINAL_EVT */ {BTA_PBA_CLIENT_RESPONSE_FINAL, BTA_PBA_CLIENT_OPENING_ST}, + /* BTA_PBA_CLIENT_GOEP_CONNECT_EVT */ {BTA_PBA_CLIENT_GOEP_CONNECT, BTA_PBA_CLIENT_OPENING_ST}, + /* BTA_PBA_CLIENT_GOEP_DISCONNECT_EVT */ {BTA_PBA_CLIENT_GOEP_DISCONNECT, BTA_PBA_CLIENT_INIT_ST}, +}; + +const uint8_t bta_pba_client_st_opened[][BTA_PBA_CLIENT_NUM_COLS] = { + /* Event Action Next state */ + /* BTA_PBA_CLIENT_API_OPEN_EVT */ {BTA_PBA_CLIENT_IGNORE, BTA_PBA_CLIENT_OPENED_ST}, + /* BTA_PBA_CLIENT_API_CLOSE_EVT */ {BTA_PBA_CLIENT_API_CLOSE, BTA_PBA_CLIENT_CLOSING_ST}, + /* BTA_PBA_CLIENT_API_REQ_EVT */ {BTA_PBA_CLIENT_API_REQ, BTA_PBA_CLIENT_REQUESTING_ST}, + /* BTA_PBA_CLIENT_DISC_RES_EVT */ {BTA_PBA_CLIENT_IGNORE, BTA_PBA_CLIENT_OPENED_ST}, + /* BTA_PBA_CLIENT_AUTHENTICATE_EVT */ {BTA_PBA_CLIENT_IGNORE, BTA_PBA_CLIENT_OPENED_ST}, + /* BTA_PBA_CLIENT_CONNECT_EVT */ {BTA_PBA_CLIENT_IGNORE, BTA_PBA_CLIENT_OPENED_ST}, + /* BTA_PBA_CLIENT_RESPONSE_EVT */ {BTA_PBA_CLIENT_FREE_RESPONSE, BTA_PBA_CLIENT_OPENED_ST}, + /* BTA_PBA_CLIENT_RESPONSE_FINAL_EVT */ {BTA_PBA_CLIENT_FREE_RESPONSE, BTA_PBA_CLIENT_OPENED_ST}, + /* BTA_PBA_CLIENT_GOEP_CONNECT_EVT */ {BTA_PBA_CLIENT_IGNORE, BTA_PBA_CLIENT_OPENED_ST}, + /* BTA_PBA_CLIENT_GOEP_DISCONNECT_EVT */ {BTA_PBA_CLIENT_GOEP_DISCONNECT, BTA_PBA_CLIENT_INIT_ST}, +}; + +const uint8_t bta_pba_client_st_getting[][BTA_PBA_CLIENT_NUM_COLS] = { + /* Event Action Next state */ + /* BTA_PBA_CLIENT_API_OPEN_EVT */ {BTA_PBA_CLIENT_IGNORE, BTA_PBA_CLIENT_REQUESTING_ST}, + /* BTA_PBA_CLIENT_API_CLOSE_EVT */ {BTA_PBA_CLIENT_FORCE_DISCONNECT, BTA_PBA_CLIENT_INIT_ST}, + /* BTA_PBA_CLIENT_API_REQ_EVT */ {BTA_PBA_CLIENT_IGNORE, BTA_PBA_CLIENT_REQUESTING_ST}, + /* BTA_PBA_CLIENT_DISC_RES_EVT */ {BTA_PBA_CLIENT_IGNORE, BTA_PBA_CLIENT_REQUESTING_ST}, + /* BTA_PBA_CLIENT_AUTHENTICATE_EVT */ {BTA_PBA_CLIENT_IGNORE, BTA_PBA_CLIENT_REQUESTING_ST}, + /* BTA_PBA_CLIENT_CONNECT_EVT */ {BTA_PBA_CLIENT_IGNORE, BTA_PBA_CLIENT_REQUESTING_ST}, + /* BTA_PBA_CLIENT_RESPONSE_EVT */ {BTA_PBA_CLIENT_RESPONSE, BTA_PBA_CLIENT_REQUESTING_ST}, + /* BTA_PBA_CLIENT_RESPONSE_FINAL_EVT */ {BTA_PBA_CLIENT_RESPONSE_FINAL, BTA_PBA_CLIENT_OPENED_ST}, + /* BTA_PBA_CLIENT_GOEP_CONNECT_EVT */ {BTA_PBA_CLIENT_IGNORE, BTA_PBA_CLIENT_REQUESTING_ST}, + /* BTA_PBA_CLIENT_GOEP_DISCONNECT_EVT */ {BTA_PBA_CLIENT_GOEP_DISCONNECT, BTA_PBA_CLIENT_INIT_ST}, +}; + +const uint8_t bta_pba_client_st_closing[][BTA_PBA_CLIENT_NUM_COLS] = { + /* Event Action Next state */ + /* BTA_PBA_CLIENT_API_OPEN_EVT */ {BTA_PBA_CLIENT_IGNORE, BTA_PBA_CLIENT_CLOSING_ST}, + /* BTA_PBA_CLIENT_API_CLOSE_EVT */ {BTA_PBA_CLIENT_FORCE_DISCONNECT, BTA_PBA_CLIENT_INIT_ST}, + /* BTA_PBA_CLIENT_API_REQ_EVT */ {BTA_PBA_CLIENT_IGNORE, BTA_PBA_CLIENT_CLOSING_ST}, + /* BTA_PBA_CLIENT_DISC_RES_EVT */ {BTA_PBA_CLIENT_IGNORE, BTA_PBA_CLIENT_CLOSING_ST}, + /* BTA_PBA_CLIENT_AUTHENTICATE_EVT */ {BTA_PBA_CLIENT_IGNORE, BTA_PBA_CLIENT_CLOSING_ST}, + /* BTA_PBA_CLIENT_CONNECT_EVT */ {BTA_PBA_CLIENT_IGNORE, BTA_PBA_CLIENT_CLOSING_ST}, + /* BTA_PBA_CLIENT_RESPONSE_EVT */ {BTA_PBA_CLIENT_IGNORE, BTA_PBA_CLIENT_CLOSING_ST}, + /* BTA_PBA_CLIENT_RESPONSE_FINAL_EVT */ {BTA_PBA_CLIENT_RESPONSE_FINAL, BTA_PBA_CLIENT_INIT_ST}, + /* BTA_PBA_CLIENT_GOEP_CONNECT_EVT */ {BTA_PBA_CLIENT_IGNORE, BTA_PBA_CLIENT_CLOSING_ST}, + /* BTA_PBA_CLIENT_GOEP_DISCONNECT_EVT */ {BTA_PBA_CLIENT_GOEP_DISCONNECT, BTA_PBA_CLIENT_INIT_ST}, +}; + +/* type for state table */ +typedef const UINT8 (*tBTA_PBA_CLIENT_ST_TBL)[BTA_PBA_CLIENT_NUM_COLS]; + +/* state table */ +const tBTA_PBA_CLIENT_ST_TBL bta_pba_client_st_tbl[] = { + bta_pba_client_st_init, + bta_pba_client_st_opening, + bta_pba_client_st_opened, + bta_pba_client_st_getting, + bta_pba_client_st_closing +}; + +/* PBA Client control block */ +#if BTA_DYNAMIC_MEMORY == FALSE +tBTA_PBA_CLIENT_CB bta_pba_client_cb; +#else +tBTA_PBA_CLIENT_CB *bta_pba_client_cb_ptr; +#endif + +static tBTA_PBA_CLIENT_CCB *allocate_ccb(void) +{ + tBTA_PBA_CLIENT_CCB *p_ccb = NULL; + for (int i = 0; i < PBA_CLIENT_MAX_CONNECTION; ++i) { + if (bta_pba_client_cb.ccb[i].allocated == 0) { + bta_pba_client_cb.ccb[i].allocated = i + 1; + p_ccb = &bta_pba_client_cb.ccb[i]; + break; + } + } + return p_ccb; +} + +static tBTA_PBA_CLIENT_CCB *find_ccb_by_handle(UINT16 handle) +{ + tBTA_PBA_CLIENT_CCB *p_ccb = NULL; + for (int i = 0; i < PBA_CLIENT_MAX_CONNECTION; ++i) { + if (bta_pba_client_cb.ccb[i].allocated != 0 && bta_pba_client_cb.ccb[i].allocated == handle) { + p_ccb = &bta_pba_client_cb.ccb[i]; + } + } + return p_ccb; +} + +static tBTA_PBA_CLIENT_CCB *find_ccb_by_goep_handle(UINT16 goep_handle) +{ + tBTA_PBA_CLIENT_CCB *p_ccb = NULL; + for (int i = 0; i < PBA_CLIENT_MAX_CONNECTION; ++i) { + if (bta_pba_client_cb.ccb[i].allocated != 0 && bta_pba_client_cb.ccb[i].goep_handle == goep_handle) { + p_ccb = &bta_pba_client_cb.ccb[i]; + } + } + return p_ccb; +} + +static tBTA_PBA_CLIENT_CCB *find_ccb_by_bd_addr(BD_ADDR bd_addr) +{ + tBTA_PBA_CLIENT_CCB *p_ccb = NULL; + for (int i = 0; i < PBA_CLIENT_MAX_CONNECTION; ++i) { + if (bta_pba_client_cb.ccb[i].allocated != 0 && bdcmp(bta_pba_client_cb.ccb[i].bd_addr, bd_addr) == 0) { + p_ccb = &bta_pba_client_cb.ccb[i]; + } + } + return p_ccb; +} + +void bta_pba_client_sm_execute(tBTA_PBA_CLIENT_CCB *p_ccb, UINT16 event, tBTA_PBA_CLIENT_DATA *p_data) +{ + tBTA_PBA_CLIENT_ST_TBL state_table; + UINT8 action; + + state_table = bta_pba_client_st_tbl[p_ccb->state]; + + event &= 0xff; + + p_ccb->state = state_table[event][BTA_PBA_CLIENT_NEXT_STATE]; + + if ((action = state_table[event][BTA_PBA_CLIENT_ACTION]) != BTA_PBA_CLIENT_IGNORE) { + (*bta_pba_client_action[action])(p_ccb, p_data); + } + + return; +} + +BOOLEAN bta_pba_client_hdl_event(BT_HDR *p_msg) +{ + tBTA_PBA_CLIENT_CCB *p_ccb = NULL; + BOOLEAN execute_sm = FALSE; + tBTA_PBA_CLIENT_CONN conn = {0}; + tBTA_PBA_CLIENT_DATA *p_data = (tBTA_PBA_CLIENT_DATA *)p_msg; + + switch (p_msg->event) { + case BTA_PBA_CLIENT_API_ENABLE_EVT: + bta_pba_client_api_enable(p_data); + break; + case BTA_PBA_CLIENT_API_DISABLE_EVT: + bta_pba_client_api_disable(p_data); + break; + case BTA_PBA_CLIENT_API_REGISTER_EVT: + bta_pba_client_api_register(p_data); + break; + case BTA_PBA_CLIENT_API_DEREGISTER_EVT: + bta_pba_client_api_deregister(p_data); + break; + case BTA_PBA_CLIENT_API_OPEN_EVT: + if (find_ccb_by_bd_addr(p_data->api_open.bd_addr) != NULL) { + /* already connected */ + conn.handle = 0; + conn.error = BTA_PBA_CLIENT_ALREADY_CONN; + bdcpy(conn.bd_addr, p_data->api_open.bd_addr); + bta_pba_client_cb.p_cback(BTA_PBA_CLIENT_CONN_OPEN_EVT, (tBTA_PBA_CLIENT *)&conn); + /* break, don't execute sm */ + break; + } + p_ccb = allocate_ccb(); + if (p_ccb == NULL) { + /* no resource to allocate ccb */ + conn.handle = 0; + conn.error = BTA_PBA_CLIENT_NO_RESOURCE; + bdcpy(conn.bd_addr, p_data->api_open.bd_addr); + bta_pba_client_cb.p_cback(BTA_PBA_CLIENT_CONN_OPEN_EVT, (tBTA_PBA_CLIENT *)&conn); + /* break, don't execute sm */ + break; + } + execute_sm = TRUE; + break; + case BTA_PBA_CLIENT_GOEP_CONNECT_EVT: + case BTA_PBA_CLIENT_GOEP_DISCONNECT_EVT: + case BTA_PBA_CLIENT_RESPONSE_EVT: + case BTA_PBA_CLIENT_RESPONSE_FINAL_EVT: + p_ccb = find_ccb_by_goep_handle(p_msg->layer_specific); + if (p_ccb == NULL) { + /* ignore event with invalid goep handle */ + break; + } + execute_sm = TRUE; + break; + default: + p_ccb = find_ccb_by_handle(p_msg->layer_specific); + if (p_ccb == NULL) { + /* ignore event with invalid handle */ + break; + } + execute_sm = TRUE; + } + + if (execute_sm) { + bta_pba_client_sm_execute(p_ccb, p_msg->event, (tBTA_PBA_CLIENT_DATA *) p_msg); + } + + return TRUE; +} + +#endif diff --git a/components/bt/host/bluedroid/bta/pba/bta_pba_client_sdp.c b/components/bt/host/bluedroid/bta/pba/bta_pba_client_sdp.c new file mode 100644 index 000000000000..901e3beab4e8 --- /dev/null +++ b/components/bt/host/bluedroid/bta/pba/bta_pba_client_sdp.c @@ -0,0 +1,280 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include "osi/allocator.h" +#include "common/bt_defs.h" +#include "stack/sdp_api.h" +#include "bta/bta_api.h" +#include "bta/bta_pba_defs.h" +#include "bta/bta_pba_client_api.h" +#include "bta_pba_client_int.h" + +#if BTA_PBA_CLIENT_INCLUDED + +/* Number of elements in service class id list. */ +#define BTA_PBA_CLIENT_NUM_SVC_ELEMS 1 + +/******************************************************************************* +** +** Function bta_pba_client_sdp_cback +** +** Description SDP callback function. +** +** +** Returns void +** +*******************************************************************************/ +static void bta_pba_client_sdp_cback(UINT16 status, void *user_data) +{ + tBTA_PBA_CLIENT_DISC_RESULT *p_buf; + tBTA_PBA_CLIENT_CCB *p_ccb = (tBTA_PBA_CLIENT_CCB *)user_data; + + APPL_TRACE_DEBUG("bta_pba_client_sdp_cback status:0x%x", status); + + if ((p_buf = (tBTA_PBA_CLIENT_DISC_RESULT *) osi_malloc(sizeof(tBTA_PBA_CLIENT_DISC_RESULT))) != NULL) { + p_buf->hdr.event = BTA_PBA_CLIENT_DISC_RES_EVT; + p_buf->hdr.layer_specific = p_ccb->allocated; + p_buf->status = status; + bta_sys_sendmsg(p_buf); + } +} + +/****************************************************************************** +** +** Function bta_pba_client_add_record +** +** Description Add PBA Client information to an SDP record. Prior to +** calling this function the application must call +** SDP_CreateRecord() to create an SDP record. +** +** Returns TRUE if function execution succeeded, +** FALSE if function execution failed. +** +******************************************************************************/ +static BOOLEAN bta_pba_client_add_record(const char *p_service_name, UINT32 sdp_handle) +{ + UINT16 svc_class_id_list[BTA_PBA_CLIENT_NUM_SVC_ELEMS]; + UINT16 version; + UINT16 profile_uuid; + BOOLEAN result = TRUE; + + APPL_TRACE_DEBUG("bta_pba_client_add_record"); + + /* add service class id list */ + svc_class_id_list[0] = UUID_SERVCLASS_PBAP_PCE; + result &= SDP_AddServiceClassIdList(sdp_handle, BTA_PBA_CLIENT_NUM_SVC_ELEMS, svc_class_id_list); + + /* add service name */ + if (p_service_name != NULL && p_service_name[0] != 0) { + result &= SDP_AddAttribute(sdp_handle, ATTR_ID_SERVICE_NAME, TEXT_STR_DESC_TYPE, + (UINT32)(strlen(p_service_name) + 1), (UINT8 *) p_service_name); + } + + /* add profile descriptor list */ + profile_uuid = UUID_SERVCLASS_PHONE_ACCESS; + version = PBAP_PCE_VERSION; + result &= SDP_AddProfileDescriptorList(sdp_handle, profile_uuid, version); + + return result; +} + +/******************************************************************************* +** +** Function bta_pba_client_create_record +** +** Description Create SDP record for registered service. +** +** +** Returns void +** +*******************************************************************************/ +void bta_pba_client_create_record(const char *p_service_name) +{ + /* add sdp record if not already registered */ + if (bta_pba_client_cb.sdp_handle == 0) { + bta_pba_client_cb.sdp_handle = SDP_CreateRecord(); + bta_pba_client_add_record(p_service_name, bta_pba_client_cb.sdp_handle); + bta_sys_add_uuid(UUID_SERVCLASS_PBAP_PCE); + } + +} + +/******************************************************************************* +** +** Function bta_pba_client_del_record +** +** Description Delete SDP record for registered service. +** +** +** Returns void +** +*******************************************************************************/ +void bta_pba_client_del_record(void) +{ + APPL_TRACE_DEBUG("bta_pba_client_del_record"); + + if (bta_pba_client_cb.sdp_handle != 0) { + SDP_DeleteRecord(bta_pba_client_cb.sdp_handle); + bta_pba_client_cb.sdp_handle = 0; + bta_sys_remove_uuid(UUID_SERVCLASS_PBAP_PCE); + } +} + +/******************************************************************************* +** +** Function bta_pba_client_sdp_find_attr +** +** Description Process SDP discovery results to find requested attribute +** +** +** Returns TRUE if results found, FALSE otherwise. +** +*******************************************************************************/ +BOOLEAN bta_pba_client_sdp_find_attr(tBTA_PBA_CLIENT_CCB *p_ccb) +{ + tSDP_DISC_REC *p_rec = NULL; + tSDP_DISC_ATTR *p_attr; + tSDP_PROTOCOL_ELEM pe; + BOOLEAN result = FALSE; + + /* loop through all records we found */ + while (TRUE) { + /* get next record; if none found, we're done */ + if ((p_rec = SDP_FindServiceInDb(p_ccb->p_disc_db, UUID_SERVCLASS_PBAP_PSE, p_rec)) == NULL) { + break; + } + + /* get rfcomm scn from proto desc list */ + if (SDP_FindProtocolListElemInRec(p_rec, UUID_PROTOCOL_RFCOMM, &pe)) { + p_ccb->peer_rfcomm_scn = (UINT8) pe.params[0]; + } + else { + /* not found, go to next record */ + continue; + } + + /* get supported repositories */ + if ((p_attr = SDP_FindAttributeInRec(p_rec, ATTR_ID_SUPPORTED_REPOSITORIES)) != NULL) { + /* Found attribute, get value */ + p_ccb->peer_supported_repo = p_attr->attr_value.v.u8; + } + else { + /* not found, clear rfcomm scn and go to next record */ + p_ccb->peer_rfcomm_scn = 0; + continue; + } + + /* get profile version */ + SDP_FindProfileVersionInRec(p_rec, UUID_SERVCLASS_PHONE_ACCESS, &p_ccb->peer_version); + + /* get GOEP L2CAP PSM */ + if ((p_attr = SDP_FindAttributeInRec(p_rec, ATTR_ID_GOEP_L2CAP_PSM)) != NULL) { + /* Found attribute, get value */ + p_ccb->peer_l2cap_psm = p_attr->attr_value.v.u16; + } + + /* try to get supported features */ + if ((p_attr = SDP_FindAttributeInRec(p_rec, ATTR_ID_PBAP_SUPPORTED_FEATURES)) != NULL) { + /* found attribute, get value */ + p_ccb->peer_supported_feat = p_attr->attr_value.v.u32; + p_ccb->send_supported_feat = TRUE; + } + else { + /* assume as default value if not found in sdp record */ + p_ccb->peer_supported_feat = BTA_PBAP_DEFAULT_SUPPORTED_FEATURES; + p_ccb->send_supported_feat = FALSE; + } + + /* found what we needed */ + result = TRUE; + break; + } + + APPL_TRACE_DEBUG("%s peer_version:0x%x, supported repositories:0x%x, supported features:0x%x", + __FUNCTION__, p_ccb->peer_version, p_ccb->peer_supported_repo, p_ccb->peer_supported_feat); + + return result; +} + +/******************************************************************************* +** +** Function bta_pba_client_do_disc +** +** Description Do service discovery. +** +** +** Returns TRUE if start service discovery successfully +** +*******************************************************************************/ +BOOLEAN bta_pba_client_do_disc(tBTA_PBA_CLIENT_CCB *p_ccb) +{ + tSDP_UUID uuid_list[1]; + UINT16 num_uuid = 1; + UINT16 attr_list[4]; + UINT8 num_attr; + BOOLEAN db_inited = FALSE; + + /* get proto list and features */ + attr_list[0] = ATTR_ID_SERVICE_CLASS_ID_LIST; + attr_list[1] = ATTR_ID_PROTOCOL_DESC_LIST; + attr_list[2] = ATTR_ID_BT_PROFILE_DESC_LIST; + attr_list[3] = ATTR_ID_GOEP_L2CAP_PSM; + attr_list[4] = ATTR_ID_SUPPORTED_REPOSITORIES; + attr_list[5] = ATTR_ID_PBAP_SUPPORTED_FEATURES; + num_attr = 6; + uuid_list[0].uu.uuid16 = UUID_SERVCLASS_PBAP_PSE; + uuid_list[0].len = LEN_UUID_16; + + if (p_ccb->p_disc_db != NULL) { + APPL_TRACE_WARNING("%s service discovery already in progress", __FUNCTION__); + return FALSE; + } + + /* allocate buffer for sdp database */ + p_ccb->p_disc_db = (tSDP_DISCOVERY_DB *) osi_malloc(BT_DEFAULT_BUFFER_SIZE); + + if (p_ccb->p_disc_db) { + /* set up service discovery database; attr happens to be attr_list len */ + db_inited = SDP_InitDiscoveryDb(p_ccb->p_disc_db, BT_DEFAULT_BUFFER_SIZE, num_uuid, + uuid_list, num_attr, attr_list); + } + + if (db_inited) { + /*start service discovery */ + /* todo: avoid p_ccb being free during sdp */ + db_inited = SDP_ServiceSearchAttributeRequest2(p_ccb->bd_addr, p_ccb->p_disc_db, + bta_pba_client_sdp_cback, p_ccb); + } + + if (!db_inited) { + /*free discover db */ + bta_pba_client_free_db(p_ccb); + APPL_TRACE_ERROR("%s start service discovery failed", __FUNCTION__); + return FALSE; + } + return TRUE; +} + +/******************************************************************************* +** +** Function bta_hf_client_free_db +** +** Description Free discovery database. +** +** +** Returns void +** +*******************************************************************************/ +void bta_pba_client_free_db(tBTA_PBA_CLIENT_CCB *p_ccb) +{ + if (p_ccb->p_disc_db != NULL) { + osi_free(p_ccb->p_disc_db); + p_ccb->p_disc_db = NULL; + } +} + +#endif diff --git a/components/bt/host/bluedroid/bta/pba/include/bta_pba_client_int.h b/components/bt/host/bluedroid/bta/pba/include/bta_pba_client_int.h new file mode 100644 index 000000000000..5d48eab9db16 --- /dev/null +++ b/components/bt/host/bluedroid/bta/pba/include/bta_pba_client_int.h @@ -0,0 +1,177 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "osi/list.h" +#include "common/bt_target.h" +#include "stack/sdp_api.h" +#include "stack/obex_api.h" +#include "bta/bta_sys.h" +#include "bta/bta_api.h" +#include "bta/bta_pba_client_api.h" + +#if BTA_PBA_CLIENT_INCLUDED + +#define PBA_CLIENT_MAX_CONNECTION 2 + +#define PBA_CLIENT_MAX_MTU L2CAP_MTU_SIZE /* RFCOMM is base on L2CAP, its MTU will smaller than this */ +#define PBA_CLIENT_MIN_MTU 255 + +enum { + /* these events are handled by the state machine */ + BTA_PBA_CLIENT_API_OPEN_EVT = BTA_SYS_EVT_START(BTA_ID_PBC), + BTA_PBA_CLIENT_API_CLOSE_EVT, + BTA_PBA_CLIENT_API_REQ_EVT, + BTA_PBA_CLIENT_DISC_RES_EVT, + BTA_PBA_CLIENT_AUTHENTICATE_EVT, + BTA_PBA_CLIENT_CONNECT_EVT, + BTA_PBA_CLIENT_RESPONSE_EVT, + BTA_PBA_CLIENT_RESPONSE_FINAL_EVT, + BTA_PBA_CLIENT_GOEP_CONNECT_EVT, + BTA_PBA_CLIENT_GOEP_DISCONNECT_EVT, + + /* these events are handled outside of the state machine */ + BTA_PBA_CLIENT_API_ENABLE_EVT, + BTA_PBA_CLIENT_API_DISABLE_EVT, + BTA_PBA_CLIENT_API_REGISTER_EVT, + BTA_PBA_CLIENT_API_DEREGISTER_EVT, +}; + +typedef enum { + BTA_PBA_CLIENT_OP_PULL_PHONE_BOOK, + BTA_PBA_CLIENT_OP_SET_PHONE_BOOK, + BTA_PBA_CLIENT_OP_PULL_VCARD_LISTING, + BTA_PBA_CLIENT_OP_PULL_VCARD_ENTRY, +} tBTA_PBA_CLIENT_OP; + +typedef struct { + BT_HDR hdr; + tBTA_PBA_CLIENT_CBACK *p_cback; +} tBTA_PBA_CLIENT_API_ENABLE; + +typedef struct { + BT_HDR hdr; + char name[BTA_SERVICE_NAME_LEN + 1]; +} tBTA_PBA_CLIENT_API_REGISTER; + +typedef struct { + BT_HDR hdr; + BD_ADDR bd_addr; + tBTA_SEC sec_mask; + UINT16 mtu; + UINT32 supported_feat; +} tBTA_PBA_CLIENT_API_OPEN; + +typedef struct { + BT_HDR hdr; + UINT8 operation; + UINT8 flags; + char *name; + UINT16 app_param_len; + UINT8 *app_param; +} tBTA_PBA_CLIENT_API_REQ; + +typedef struct { + BT_HDR hdr; + UINT16 status; +} tBTA_PBA_CLIENT_DISC_RESULT; + +typedef struct { + BT_HDR hdr; + UINT16 our_mtu; + UINT16 peer_mtu; +} tBTA_PBA_CLIENT_GOEP_CONNECT; + +typedef struct { + BT_HDR hdr; + UINT16 reason; +} tBTA_PBA_CLIENT_GOEP_DISCONNECT; + +typedef struct { + BT_HDR hdr; + BT_HDR *pkt; + UINT8 opcode; + BOOLEAN srm_en; + BOOLEAN srm_wait; +} tBTA_PBA_CLIENT_GOEP_RESPONSE; + +typedef union { + BT_HDR hdr; + tBTA_PBA_CLIENT_API_ENABLE api_enable; + tBTA_PBA_CLIENT_API_REGISTER api_register; + tBTA_PBA_CLIENT_API_OPEN api_open; + tBTA_PBA_CLIENT_API_REQ api_req; + tBTA_PBA_CLIENT_DISC_RESULT disc_result; + tBTA_PBA_CLIENT_GOEP_CONNECT goep_connect; + tBTA_PBA_CLIENT_GOEP_DISCONNECT goep_disconnect; + tBTA_PBA_CLIENT_GOEP_RESPONSE goep_response; +} tBTA_PBA_CLIENT_DATA; + +typedef struct { + BD_ADDR bd_addr; /* peer BD address */ + tSDP_DISCOVERY_DB *p_disc_db; /* pointer to discovery database */ + tBTA_SEC sec_mask; /* security mask */ + UINT16 peer_version; /* peer profile version */ + UINT16 peer_l2cap_psm; /* peer l2cap psm */ + UINT8 peer_rfcomm_scn; /* peer rfcomm scn */ + UINT8 peer_supported_repo; /* peer supported repositories */ + UINT32 peer_supported_feat; /* peer supported features */ + UINT32 our_supported_feat; /* we supported features */ + BOOLEAN send_supported_feat; /* whether we should send supported features in connect request */ + UINT16 goep_handle; /* goep connection handle */ + UINT32 goep_cid; /* goep connection id */ + UINT16 max_rx; /* max rx bytes */ + UINT16 max_tx; /* max tx bytes */ + BOOLEAN authenticate; /* whether we are authenticated */ + tBTA_PBA_CLIENT_OP operation; /* ongoing or last operations */ + UINT8 state; /* main state machine */ + UINT8 allocated; /* index + 1 if allocated, otherwise 0 */ +} tBTA_PBA_CLIENT_CCB; + +typedef struct { + tBTA_PBA_CLIENT_CCB ccb[PBA_CLIENT_MAX_CONNECTION]; /* connection control block */ + tBTA_PBA_CLIENT_CBACK *p_cback; /* message callback to upper */ + UINT32 sdp_handle; /* sdp record handle */ + UINT8 trace_level; /* debug trace level */ +} tBTA_PBA_CLIENT_CB; + +#if BTA_DYNAMIC_MEMORY == FALSE +extern tBTA_PBA_CLIENT_CB bta_pba_client_cb; +#else +extern tBTA_PBA_CLIENT_CB *bta_pba_client_cb_ptr; +#define bta_pba_client_cb (*bta_pba_client_cb_ptr) +#endif + +extern void bta_pba_client_api_enable(tBTA_PBA_CLIENT_DATA *p_data); +extern void bta_pba_client_api_disable(tBTA_PBA_CLIENT_DATA *p_data); +extern void bta_pba_client_api_register(tBTA_PBA_CLIENT_DATA *p_data); +extern void bta_pba_client_api_deregister(tBTA_PBA_CLIENT_DATA *p_data); +extern void bta_pba_client_api_open(tBTA_PBA_CLIENT_CCB *p_ccb, tBTA_PBA_CLIENT_DATA *p_data); +extern void bta_pba_client_api_close(tBTA_PBA_CLIENT_CCB *p_ccb, tBTA_PBA_CLIENT_DATA *p_data); +extern void bta_pba_client_api_req(tBTA_PBA_CLIENT_CCB *p_ccb, tBTA_PBA_CLIENT_DATA *p_data); +extern void bta_pba_client_do_connect(tBTA_PBA_CLIENT_CCB *p_ccb, tBTA_PBA_CLIENT_DATA *p_data); +extern void bta_pba_client_authenticate(tBTA_PBA_CLIENT_CCB *p_ccb, tBTA_PBA_CLIENT_DATA *p_data); +extern void bta_pba_client_force_disconnect(tBTA_PBA_CLIENT_CCB *p_ccb, tBTA_PBA_CLIENT_DATA *p_data); + +extern void bta_pba_client_connect(tBTA_PBA_CLIENT_CCB *p_ccb, tBTA_PBA_CLIENT_DATA *p_data); +extern void bta_pba_client_response(tBTA_PBA_CLIENT_CCB *p_ccb, tBTA_PBA_CLIENT_DATA *p_data); +extern void bta_pba_client_response_final(tBTA_PBA_CLIENT_CCB *p_ccb, tBTA_PBA_CLIENT_DATA *p_data); +void bta_pba_client_goep_connect(tBTA_PBA_CLIENT_CCB *p_ccb, tBTA_PBA_CLIENT_DATA *p_data); +void bta_pba_client_goep_disconnect(tBTA_PBA_CLIENT_CCB *p_ccb, tBTA_PBA_CLIENT_DATA *p_data); +void bta_pba_client_free_response(tBTA_PBA_CLIENT_CCB *p_ccb, tBTA_PBA_CLIENT_DATA *p_data); +void bta_pba_client_free_sdp_db(tBTA_PBA_CLIENT_CCB *p_ccb, tBTA_PBA_CLIENT_DATA *p_data); + +extern void bta_pba_client_create_record(const char *p_service_name); +extern void bta_pba_client_del_record(void); +extern BOOLEAN bta_pba_client_sdp_find_attr(tBTA_PBA_CLIENT_CCB *p_ccb); +extern BOOLEAN bta_pba_client_do_disc(tBTA_PBA_CLIENT_CCB *p_ccb); +extern void bta_pba_client_free_db(tBTA_PBA_CLIENT_CCB *p_ccb); + +extern void bta_pba_client_sm_execute(tBTA_PBA_CLIENT_CCB *p_ccb, UINT16 event, tBTA_PBA_CLIENT_DATA *p_data); +extern BOOLEAN bta_pba_client_hdl_event(BT_HDR *p_msg); + +#endif diff --git a/components/bt/host/bluedroid/btc/profile/std/include/btc_pba_client.h b/components/bt/host/bluedroid/btc/profile/std/include/btc_pba_client.h new file mode 100644 index 000000000000..0a718fa30a03 --- /dev/null +++ b/components/bt/host/bluedroid/btc/profile/std/include/btc_pba_client.h @@ -0,0 +1,90 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "common/bt_defs.h" +#include "esp_pbac_api.h" + +#if BTC_PBA_CLIENT_INCLUDED + +typedef enum { + BTC_PBA_CLIENT_INIT_EVT = 0, + BTC_PBA_CLIENT_DEINIT_EVT, + BTC_PBA_CLIENT_CONNECT_EVT, + BTC_PBA_CLIENT_DISCONNECT_EVT, + BTC_PBA_CLIENT_PULL_PHONE_BOOK_EVT, + BTC_PBA_CLIENT_SET_PHONE_BOOK_EVT, + BTC_PBA_CLIENT_SET_PHONE_BOOK2_EVT, + BTC_PBA_CLIENT_PULL_VCARD_LISTING_EVT, + BTC_PBA_CLIENT_PULL_VCARD_ENTRY_EVT +} BTC_PBA_CLIENT_EVT; + +/* equal to BTA max conn */ +#define BTC_PBA_CLIENT_MAX_CONN_NUM 2 + +typedef struct { + uint16_t handle; + bt_bdaddr_t bd_addr; + char *path; + uint16_t path_len; + uint16_t path_pos; + bool busy; +} btc_pba_client_ccb_t; + +typedef struct { + btc_pba_client_ccb_t ccb[BTC_PBA_CLIENT_MAX_CONN_NUM]; +} btc_pba_client_cb_t; + +typedef union { + // BTC_PBA_CLIENT_CONNECT_EVT + struct pba_client_connect_arg { + bt_bdaddr_t bd_addr; + } connect; + + // BTC_PBA_CLIENT_DISCONNECT_EVT + struct pba_client_disconnect_arg { + uint16_t handle; + } disconnect; + + // BTC_PBA_CLIENT_PULL_PHONE_BOOK_EVT + struct pba_client_pull_phone_book_arg { + uint16_t handle; + char *name; + bool include_app_param; + esp_pbac_pull_phone_book_app_param_t app_param; + } pull_phone_book; + + // BTC_PBA_CLIENT_SET_PHONE_BOOK_EVT + struct pba_client_set_phone_book_arg { + uint16_t handle; + uint8_t flags; + char *name; + } set_phone_book; + + // BTC_PBA_CLIENT_PULL_VCARD_LISTING_EVT + struct pba_client_pull_vcard_listing_arg { + uint16_t handle; + char *name; + bool include_app_param; + esp_pbac_pull_vcard_listing_app_param_t app_param; + } pull_vcard_listing; + + // BTC_PBA_CLIENT_PULL_VCARD_ENTRY_EVT + struct pba_client_pull_vcard_entry_arg { + uint16_t handle; + char *name; + bool include_app_param; + esp_pbac_pull_vcard_entry_app_param_t app_param; + } pull_vcard_entry; +} btc_pba_client_args_t; + +void btc_pba_client_call_handler(btc_msg_t *msg); +void btc_pba_client_cb_handler(btc_msg_t *msg); +void btc_pba_client_args_deep_free(btc_msg_t *msg); +void btc_pba_client_args_deep_copy(btc_msg_t *msg, void *p_dest, void *p_src); + +#endif diff --git a/components/bt/host/bluedroid/btc/profile/std/pba/btc_pba_client.c b/components/bt/host/bluedroid/btc/profile/std/pba/btc_pba_client.c new file mode 100644 index 000000000000..f1ef0981f3ce --- /dev/null +++ b/components/bt/host/bluedroid/btc/profile/std/pba/btc_pba_client.c @@ -0,0 +1,971 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "osi/allocator.h" + +#include "bta/bta_api.h" +#include "bta/bta_pba_defs.h" +#include "bta/bta_pba_client_api.h" +#include "btc/btc_profile_queue.h" +#include "btc/btc_manage.h" +#include "btc/btc_task.h" +#include "btc_pba_client.h" +#include "esp_pbac_api.h" + +#if BTC_PBA_CLIENT_INCLUDED + +#define BTC_PBA_CLIENT_SECURITY (BTA_SEC_AUTHENTICATE | BTA_SEC_ENCRYPT) + +static bool s_btc_pba_client_init = 0; + +btc_pba_client_cb_t btc_pba_client_cb; + +static void bte_pba_client_evt(tBTA_PBA_CLIENT_EVT event, tBTA_PBA_CLIENT *p_data) +{ + bt_status_t status; + int param_len = 0; + bool ignore = false; + + switch (event) { + case BTA_PBA_CLIENT_CONN_OPEN_EVT: + case BTA_PBA_CLIENT_CONN_CLOSE_EVT: + param_len = sizeof(tBTA_PBA_CLIENT_CONN); + break; + case BTA_PBA_CLIENT_PULL_PHONE_BOOK_RSP_EVT: + case BTA_PBA_CLIENT_SET_PHONE_BOOK_RSP_EVT: + case BTA_PBA_CLIENT_PULL_VCARD_LISTING_RSP_EVT: + case BTA_PBA_CLIENT_PULL_VCARD_ENTRY_RSP_EVT: + param_len = sizeof(tBTA_PBA_CLIENT_RESPONSE); + break; + case BTA_PBA_CLIENT_REGISTER_EVT: + param_len = 0; + break; + case BTA_PBA_CLIENT_DISABLE_EVT: + param_len = 0; + break; + case BTA_PBA_CLIENT_ENABLE_EVT: + case BTA_PBA_CLIENT_DEREGISTER_EVT: + default: + ignore = true; + break; + } + + if (ignore) { + return; + } + + btc_msg_t msg; + msg.sig = BTC_SIG_API_CB; + msg.pid = BTC_PID_PBA_CLIENT; + msg.act = event; + + status = btc_transfer_context(&msg, p_data, param_len, NULL, NULL); + if (status != BT_STATUS_SUCCESS) { + BTC_TRACE_ERROR("context transfer failed"); + } +} + +static void btc_pba_client_callback_to_app(esp_pbac_event_t event, esp_pbac_param_t *param) +{ + esp_pbac_callback_t callback = (esp_pbac_callback_t)btc_profile_cb_get(BTC_PID_PBA_CLIENT); + if (callback) { + callback(event, param); + } +} + +static void btc_pba_client_init(void) +{ + if (!s_btc_pba_client_init) { + s_btc_pba_client_init = true; + memset(&btc_pba_client_cb, 0, sizeof(btc_pba_client_cb_t)); + /* enable pba client */ + BTA_PbaClientEnable(bte_pba_client_evt); + /* register sdp record */ + BTA_PbaClientRegister("Phonebook Access PCE"); + } +} + +static void btc_pba_client_deinit(void) +{ + if (s_btc_pba_client_init) { + s_btc_pba_client_init = false; + /* deregister sdp record */ + BTA_PbaClientDeregister(); + /* disable pba client */ + BTA_PbaClientDisable(); + } +} + +static BOOLEAN is_connected(bt_bdaddr_t *bd_addr) +{ + for (int i = 0; i < BTC_PBA_CLIENT_MAX_CONN_NUM; ++i) { + if (btc_pba_client_cb.ccb[i].handle != 0 && bdcmp(bd_addr->address, btc_pba_client_cb.ccb[i].bd_addr.address) == 0) { + return TRUE; + } + } + + return FALSE; +} + +static bt_status_t connect_int(bt_bdaddr_t *bd_addr, uint16_t uuid) +{ + if (is_connected(bd_addr)) { + return BT_STATUS_BUSY; + } + + BTA_PbaClientOpen(bd_addr->address, BTC_PBA_CLIENT_SECURITY, (uint32_t)BTC_PBA_SUPPORTED_FEAT, (uint16_t)BTC_PBA_PREFERRED_MTU); + + return BT_STATUS_SUCCESS; +} + +static void btc_pba_client_connect(bt_bdaddr_t *bd_addr) +{ + if (!s_btc_pba_client_init) { + return; + } + + btc_queue_connect(UUID_SERVCLASS_PBAP_PCE, bd_addr, connect_int); +} + +static void btc_pba_client_disconnect(uint16_t handle) +{ + do { + if (!s_btc_pba_client_init) { + break; + } + + if (handle == 0 || handle > BTC_PBA_CLIENT_MAX_CONN_NUM) { + /* invalid handle value */ + break; + } + + btc_pba_client_ccb_t *p_ccb = &btc_pba_client_cb.ccb[handle - 1]; + if (p_ccb->handle != handle) { + /* not connect */ + break; + } + + BTA_PbaClientClose(handle); + } while (0); +} + +static bool btc_pba_client_pull_phone_book(uint16_t handle, char *name, bool include_app_param, esp_pbac_pull_phone_book_app_param_t *app_param) +{ + bt_status_t err = BT_STATUS_FAIL; + uint8_t *app_param_buff = NULL; + uint16_t app_param_len = 0; + + do { + if (!s_btc_pba_client_init) { + /* pba client not init */ + err = BT_STATUS_NOT_READY; + break; + } + + if (handle == 0 || handle > BTC_PBA_CLIENT_MAX_CONN_NUM) { + /* invalid handle value */ + err = BT_STATUS_PARM_INVALID; + break; + } + + btc_pba_client_ccb_t *p_ccb = &btc_pba_client_cb.ccb[handle - 1]; + if (p_ccb->handle != handle) { + /* not connect */ + err = BT_STATUS_PARM_INVALID; + break; + } + + if (p_ccb->busy) { + /* busy */ + err = BT_STATUS_BUSY; + break; + } + + if (include_app_param) { + app_param_buff = osi_malloc(BTA_PBAP_PULL_PHONE_BOOK_APP_PARAM_BUFF_SIZE_MIN); + if (app_param_buff == NULL) { + err = BT_STATUS_NOMEM; + break; + } + uint8_t *p = app_param_buff; + if (app_param->include_property_selector) { + UINT8_TO_BE_STREAM(p, BTA_PBAP_APP_PARAM_PROPERTY_SELECTOR); + UINT8_TO_BE_STREAM(p, BTA_PBAP_APP_PARAM_LENGTH_PROPERTY_SELECTOR); + UINT64_TO_BE_STREAM(p, app_param->property_selector); + } + if (app_param->include_format) { + UINT8_TO_BE_STREAM(p, BTA_PBAP_APP_PARAM_FORMAT); + UINT8_TO_BE_STREAM(p, BTA_PBAP_APP_PARAM_LENGTH_FORMAT); + UINT8_TO_BE_STREAM(p, app_param->format); + } + if (app_param->include_max_list_count) { + UINT8_TO_BE_STREAM(p, BTA_PBAP_APP_PARAM_MAX_LIST_COUNT); + UINT8_TO_BE_STREAM(p, BTA_PBAP_APP_PARAM_LENGTH_MAX_LIST_COUNT); + UINT16_TO_BE_STREAM(p, app_param->max_list_count); + } + if (app_param->include_list_start_offset) { + UINT8_TO_BE_STREAM(p, BTA_PBAP_APP_PARAM_LIST_START_OFFSET); + UINT8_TO_BE_STREAM(p, BTA_PBAP_APP_PARAM_LENGTH_LIST_START_OFFSET); + UINT16_TO_BE_STREAM(p, app_param->list_start_offset); + } + if (app_param->include_reset_new_missed_calls) { + UINT8_TO_BE_STREAM(p, BTA_PBAP_APP_PARAM_RESET_NEW_MISSED_CALLS); + UINT8_TO_BE_STREAM(p, BTA_PBAP_APP_PARAM_LENGTH_RESET_NEW_MISSED_CALLS); + UINT8_TO_BE_STREAM(p, app_param->reset_new_missed_calls); + } + if (app_param->include_vcard_selector) { + UINT8_TO_BE_STREAM(p, BTA_PBAP_APP_PARAM_VCARD_SELECTOR); + UINT8_TO_BE_STREAM(p, BTA_PBAP_APP_PARAM_LENGTH_VCARD_SELECTOR); + UINT64_TO_BE_STREAM(p, app_param->vcard_selector); + } + if (app_param->include_vcard_selector_operator) { + UINT8_TO_BE_STREAM(p, BTA_PBAP_APP_PARAM_VCARD_SELECTOR_OPERATOR); + UINT8_TO_BE_STREAM(p, BTA_PBAP_APP_PARAM_LENGTH_VCARD_SELECTOR_OPERATOR); + UINT8_TO_BE_STREAM(p, app_param->vcard_selector_operator); + } + app_param_len = p - app_param_buff; + assert(app_param_len <= BTA_PBAP_PULL_PHONE_BOOK_APP_PARAM_BUFF_SIZE_MIN); + if (app_param_len == 0) { + /* user give us an empty app param, allow but not recommend */ + osi_free(app_param_buff); + app_param_buff = NULL; + } + } + + p_ccb->busy = true; + BTA_PbaClientPullPhoneBook(handle, name, app_param_buff, app_param_len); + err = BT_STATUS_SUCCESS; + } while (0); + + if (err != BT_STATUS_SUCCESS) { + BTC_TRACE_WARNING("%s failed, handle: %d, reason: %d", __FUNCTION__, handle, err); + return false; + } + + return true; +} + +static bool btc_pba_client_set_phone_book(uint16_t handle, uint8_t flags, char *name) +{ + bt_status_t err = BT_STATUS_FAIL; + + do { + if (!s_btc_pba_client_init) { + /* pba client not init */ + err = BT_STATUS_NOT_READY; + break; + } + + if (handle == 0 || handle > BTC_PBA_CLIENT_MAX_CONN_NUM) { + /* invalid handle value */ + err = BT_STATUS_PARM_INVALID; + break; + } + + btc_pba_client_ccb_t *p_ccb = &btc_pba_client_cb.ccb[handle - 1]; + if (p_ccb->handle != handle) { + /* not connect */ + err = BT_STATUS_PARM_INVALID; + break; + } + + if (p_ccb->busy) { + /* busy */ + err = BT_STATUS_BUSY; + break; + } + + p_ccb->busy = true; + BTA_PbaClientSetPhoneBook(handle, flags, (char *)name); + err = BT_STATUS_SUCCESS; + } while (0); + + if (err != BT_STATUS_SUCCESS) { + BTC_TRACE_WARNING("%s failed, handle: %d, reason: %d", __FUNCTION__, handle, err); + return false; + } + + return true; +} + +static bool btc_pba_client_set_phone_book2(uint16_t handle, char *path) +{ + bt_status_t err = BT_STATUS_FAIL; + + do { + if (!s_btc_pba_client_init) { + /* pba client not init */ + err = BT_STATUS_NOT_READY; + break; + } + + if (handle == 0 || handle > BTC_PBA_CLIENT_MAX_CONN_NUM) { + /* invalid handle value */ + err = BT_STATUS_PARM_INVALID; + break; + } + + btc_pba_client_ccb_t *p_ccb = &btc_pba_client_cb.ccb[handle - 1]; + if (p_ccb->handle != handle) { + /* not connect */ + err = BT_STATUS_PARM_INVALID; + break; + } + + if (p_ccb->busy) { + /* busy */ + err = BT_STATUS_BUSY; + break; + } + + p_ccb->busy = true; + if (path != NULL) { + p_ccb->path_len = strlen(path) + 1; + /* ignore the first slash */ + if (path[0] == '/') { + p_ccb->path_pos = 1; + } + else { + p_ccb->path_pos = 0; + } + /* since we use absolute path, treat empty path as go to ROOT */ + if (p_ccb->path_len == p_ccb->path_pos + 1) { + p_ccb->path_len = 0; + p_ccb->path_pos = 0; + osi_free(path); + path = NULL; + } + else { + p_ccb->path = path; + } + } + /* anyway, go to ROOT first */ + char *empty_name = osi_malloc(1); + assert(empty_name != NULL); + *empty_name = '\0'; + BTA_PbaClientSetPhoneBook(handle, ESP_PBAC_SET_PHONE_BOOK_FLAGS_ROOT, empty_name); + err = BT_STATUS_SUCCESS; + } while (0); + + if (err != BT_STATUS_SUCCESS) { + BTC_TRACE_WARNING("%s failed, handle: %d, reason: %d", __FUNCTION__, handle, err); + return false; + } + + return true; +} + +static bool btc_pba_client_pull_vcard_listing(uint16_t handle, char *name, bool include_app_param, esp_pbac_pull_vcard_listing_app_param_t *app_param) +{ + bt_status_t err = BT_STATUS_FAIL; + uint8_t *app_param_buff = NULL; + uint16_t app_param_len = 0; + + do { + if (!s_btc_pba_client_init) { + /* pba client not init */ + err = BT_STATUS_NOT_READY; + break; + } + + if (handle == 0 || handle > BTC_PBA_CLIENT_MAX_CONN_NUM) { + /* invalid handle value */ + err = BT_STATUS_PARM_INVALID; + break; + } + + btc_pba_client_ccb_t *p_ccb = &btc_pba_client_cb.ccb[handle - 1]; + if (p_ccb->handle != handle) { + /* not connect */ + err = BT_STATUS_PARM_INVALID; + break; + } + + if (p_ccb->busy) { + /* busy */ + err = BT_STATUS_BUSY; + break; + } + + if (include_app_param) { + uint8_t search_value_len = 0; + if (app_param->include_search_value) { + search_value_len = strlen(app_param->search_value) + 1; + } + app_param_buff = osi_malloc(BTA_PBAP_PULL_VCARD_LISTING_APP_PARAM_BUFF_SIZE_MIN + search_value_len); + if (app_param_buff == NULL) { + err = BT_STATUS_NOMEM; + break; + } + + uint8_t *p = app_param_buff; + if (app_param->include_order) { + UINT8_TO_BE_STREAM(p, BTA_PBAP_APP_PARAM_ORDER); + UINT8_TO_BE_STREAM(p, BTA_PBAP_APP_PARAM_LENGTH_ORDER); + UINT8_TO_BE_STREAM(p, app_param->order); + } + if (app_param->include_search_value) { + UINT8_TO_BE_STREAM(p, BTA_PBAP_APP_PARAM_SEARCH_VALUE); + UINT8_TO_BE_STREAM(p, search_value_len); + memcpy(p, app_param->search_value, search_value_len); + p += search_value_len; + } + if (app_param->include_search_property) { + UINT8_TO_BE_STREAM(p, BTA_PBAP_APP_PARAM_SEARCH_PROPERTY); + UINT8_TO_BE_STREAM(p, BTA_PBAP_APP_PARAM_LENGTH_SEARCH_PROPERTY); + UINT8_TO_BE_STREAM(p, app_param->search_property); + } + if (app_param->include_max_list_count) { + UINT8_TO_BE_STREAM(p, BTA_PBAP_APP_PARAM_MAX_LIST_COUNT); + UINT8_TO_BE_STREAM(p, BTA_PBAP_APP_PARAM_LENGTH_MAX_LIST_COUNT); + UINT16_TO_BE_STREAM(p, app_param->max_list_count); + } + if (app_param->include_list_start_offset) { + UINT8_TO_BE_STREAM(p, BTA_PBAP_APP_PARAM_LIST_START_OFFSET); + UINT8_TO_BE_STREAM(p, BTA_PBAP_APP_PARAM_LENGTH_LIST_START_OFFSET); + UINT16_TO_BE_STREAM(p, app_param->list_start_offset); + } + if (app_param->include_reset_new_missed_calls) { + UINT8_TO_BE_STREAM(p, BTA_PBAP_APP_PARAM_RESET_NEW_MISSED_CALLS); + UINT8_TO_BE_STREAM(p, BTA_PBAP_APP_PARAM_LENGTH_RESET_NEW_MISSED_CALLS); + UINT8_TO_BE_STREAM(p, app_param->reset_new_missed_calls); + } + if (app_param->include_vcard_selector) { + UINT8_TO_BE_STREAM(p, BTA_PBAP_APP_PARAM_VCARD_SELECTOR); + UINT8_TO_BE_STREAM(p, BTA_PBAP_APP_PARAM_LENGTH_VCARD_SELECTOR); + UINT64_TO_BE_STREAM(p, app_param->vcard_selector); + } + if (app_param->include_vcard_selector_operator) { + UINT8_TO_BE_STREAM(p, BTA_PBAP_APP_PARAM_VCARD_SELECTOR_OPERATOR); + UINT8_TO_BE_STREAM(p, BTA_PBAP_APP_PARAM_LENGTH_VCARD_SELECTOR_OPERATOR); + UINT8_TO_BE_STREAM(p, app_param->vcard_selector_operator); + } + app_param_len = p - app_param_buff; + assert(app_param_len <= BTA_PBAP_PULL_VCARD_LISTING_APP_PARAM_BUFF_SIZE_MIN + search_value_len); + if (app_param_len == 0) { + /* user give us an empty app param, allow but not recommend */ + osi_free(app_param_buff); + app_param_buff = NULL; + } + /* free search_value (allocated by deep_copy) */ + if (app_param->include_search_value && app_param->search_value) { + osi_free(app_param->search_value); + app_param->search_value = NULL; + } + } + + p_ccb->busy = true; + BTA_PbaClientPullvCardListing(handle, (char *)name, app_param_buff, app_param_len); + err = BT_STATUS_SUCCESS; + } while (0); + + if (err != BT_STATUS_SUCCESS) { + BTC_TRACE_WARNING("%s failed, handle: %d, reason: %d", __FUNCTION__, handle, err); + return false; + } + + return true; +} + +static bool btc_pba_client_pull_vcard_entry(uint16_t handle, char *name, bool include_app_param, esp_pbac_pull_vcard_entry_app_param_t *app_param) +{ + bt_status_t err = BT_STATUS_FAIL; + uint8_t *app_param_buff = NULL; + uint16_t app_param_len = 0; + + do { + if (!s_btc_pba_client_init) { + /* pba client not init */ + err = BT_STATUS_NOT_READY; + break; + } + + if (handle == 0 || handle > BTC_PBA_CLIENT_MAX_CONN_NUM) { + /* invalid handle value */ + err = BT_STATUS_PARM_INVALID; + break; + } + + btc_pba_client_ccb_t *p_ccb = &btc_pba_client_cb.ccb[handle - 1]; + if (p_ccb->handle != handle) { + /* not connect */ + err = BT_STATUS_PARM_INVALID; + break; + } + + if (p_ccb->busy) { + /* busy */ + err = BT_STATUS_BUSY; + break; + } + + if (include_app_param) { + app_param_buff = osi_malloc(BTA_PBAP_PULL_VCARD_ENTRY_APP_PARAM_BUFF_SIZE_MIN); + if (app_param_buff == NULL) { + err = BT_STATUS_NOMEM; + break; + } + uint8_t *p = app_param_buff; + if (app_param->include_property_selector) { + UINT8_TO_BE_STREAM(p, BTA_PBAP_APP_PARAM_PROPERTY_SELECTOR); + UINT8_TO_BE_STREAM(p, BTA_PBAP_APP_PARAM_LENGTH_PROPERTY_SELECTOR); + UINT64_TO_BE_STREAM(p, app_param->property_selector); + } + if (app_param->include_format) { + UINT8_TO_BE_STREAM(p, BTA_PBAP_APP_PARAM_FORMAT); + UINT8_TO_BE_STREAM(p, BTA_PBAP_APP_PARAM_LENGTH_FORMAT); + UINT8_TO_BE_STREAM(p, app_param->format); + } + app_param_len = p - app_param_buff; + assert(app_param_len <= BTA_PBAP_PULL_VCARD_ENTRY_APP_PARAM_BUFF_SIZE_MIN); + if (app_param_len == 0) { + /* user give us an empty app param, allow but not recommend */ + osi_free(app_param_buff); + app_param_buff = NULL; + } + } + + p_ccb->busy = true; + BTA_PbaClientPullvCardEntry(handle, (char *)name, app_param_buff, app_param_len); + err = BT_STATUS_SUCCESS; + } while (0); + + if (err != BT_STATUS_SUCCESS) { + BTC_TRACE_WARNING("%s failed, handle: %d, reason: %d", __FUNCTION__, handle, err); + return false; + } + + return true; +} + +void btc_pba_client_args_deep_copy(btc_msg_t *msg, void *p_dest, void *p_src) +{ + btc_pba_client_args_t *dst = (btc_pba_client_args_t *)p_dest; + btc_pba_client_args_t *src = (btc_pba_client_args_t *)p_src; + size_t len; + + switch (msg->act) { + case BTC_PBA_CLIENT_PULL_PHONE_BOOK_EVT: + len = strlen(src->pull_phone_book.name) + 1; + dst->pull_phone_book.name = (char *)osi_malloc(len); + if (dst->pull_phone_book.name) { + memcpy(dst->pull_phone_book.name, src->pull_phone_book.name, len); + } else { + BTC_TRACE_ERROR("%s %d no mem\n", __FUNCTION__, msg->act); + } + break; + case BTC_PBA_CLIENT_PULL_VCARD_LISTING_EVT: + len = strlen(src->pull_vcard_listing.name) + 1; + dst->pull_vcard_listing.name = (char *)osi_malloc(len); + if (dst->pull_vcard_listing.name) { + memcpy(dst->pull_vcard_listing.name, src->pull_vcard_listing.name, len); + } else { + BTC_TRACE_ERROR("%s %d no mem\n", __FUNCTION__, msg->act); + } + if (src->pull_vcard_listing.include_app_param && src->pull_vcard_listing.app_param.include_search_value) { + len = strlen(src->pull_vcard_listing.app_param.search_value) + 1; + dst->pull_vcard_listing.app_param.search_value = (char *)osi_malloc(len); + if (dst->pull_vcard_listing.app_param.search_value) { + memcpy(dst->pull_vcard_listing.app_param.search_value, src->pull_vcard_listing.app_param.search_value, len); + } else { + BTC_TRACE_ERROR("%s %d no mem\n", __FUNCTION__, msg->act); + } + } + break; + case BTC_PBA_CLIENT_PULL_VCARD_ENTRY_EVT: + len = strlen(src->pull_vcard_entry.name) + 1; + dst->pull_vcard_entry.name = (char *)osi_malloc(len); + if (dst->pull_vcard_entry.name) { + memcpy(dst->pull_vcard_entry.name, src->pull_vcard_entry.name, len); + } else { + BTC_TRACE_ERROR("%s %d no mem\n", __FUNCTION__, msg->act); + } + break; + case BTC_PBA_CLIENT_SET_PHONE_BOOK_EVT: + case BTC_PBA_CLIENT_SET_PHONE_BOOK2_EVT: + /* set phone book name may be NULL */ + if (src->set_phone_book.name) { + len = strlen(src->set_phone_book.name) + 1; + dst->set_phone_book.name = (char *)osi_malloc(len); + if (dst->set_phone_book.name) { + memcpy(dst->set_phone_book.name, src->set_phone_book.name, len); + } else { + BTC_TRACE_ERROR("%s %d no mem\n", __FUNCTION__, msg->act); + } + } + break; + default: + BTC_TRACE_DEBUG("%s Unhandled deep copy %d\n", __FUNCTION__, msg->act); + UNUSED(dst); + UNUSED(src); + UNUSED(len); + break; + } +} + +void btc_pba_client_args_deep_free(btc_msg_t *msg) +{ + btc_pba_client_args_t *arg = (btc_pba_client_args_t *)msg->arg; + + switch (msg->act) { + case BTC_PBA_CLIENT_PULL_PHONE_BOOK_EVT: + if (arg->pull_phone_book.name) { + osi_free(arg->pull_phone_book.name); + } + break; + case BTC_PBA_CLIENT_PULL_VCARD_LISTING_EVT: + if (arg->pull_vcard_listing.name) { + osi_free(arg->pull_vcard_listing.name); + } + if (arg->pull_vcard_listing.include_app_param + && arg->pull_vcard_listing.app_param.include_search_value + && arg->pull_vcard_listing.app_param.search_value) { + osi_free(arg->pull_vcard_listing.app_param.search_value); + } + break; + case BTC_PBA_CLIENT_PULL_VCARD_ENTRY_EVT: + if (arg->pull_vcard_entry.name) { + osi_free(arg->pull_vcard_entry.name); + } + break; + case BTC_PBA_CLIENT_SET_PHONE_BOOK_EVT: + case BTC_PBA_CLIENT_SET_PHONE_BOOK2_EVT: + if (arg->set_phone_book.name) { + osi_free(arg->set_phone_book.name); + } + break; + default: + BTC_TRACE_DEBUG("%s Unhandled deep free %d\n", __FUNCTION__, msg->act); + UNUSED(arg); + break; + } +} + +void btc_pba_client_call_handler(btc_msg_t *msg) +{ + bool ret = true; + btc_pba_client_args_t *arg = (btc_pba_client_args_t *)(msg->arg); + switch (msg->act) { + case BTC_PBA_CLIENT_INIT_EVT: + btc_pba_client_init(); + break; + case BTC_PBA_CLIENT_DEINIT_EVT: + btc_pba_client_deinit(); + break; + case BTC_PBA_CLIENT_CONNECT_EVT: + btc_pba_client_connect(&arg->connect.bd_addr); + break; + case BTC_PBA_CLIENT_DISCONNECT_EVT: + btc_pba_client_disconnect(arg->disconnect.handle); + break; + case BTC_PBA_CLIENT_PULL_PHONE_BOOK_EVT: + ret = btc_pba_client_pull_phone_book(arg->pull_phone_book.handle, arg->pull_phone_book.name, arg->pull_phone_book.include_app_param, &arg->pull_phone_book.app_param); + break; + case BTC_PBA_CLIENT_SET_PHONE_BOOK_EVT: + ret = btc_pba_client_set_phone_book(arg->set_phone_book.handle, arg->set_phone_book.flags, arg->set_phone_book.name); + break; + case BTC_PBA_CLIENT_SET_PHONE_BOOK2_EVT: + ret = btc_pba_client_set_phone_book2(arg->set_phone_book.handle, arg->set_phone_book.name); + break; + case BTC_PBA_CLIENT_PULL_VCARD_LISTING_EVT: + ret = btc_pba_client_pull_vcard_listing(arg->pull_vcard_listing.handle, arg->pull_vcard_listing.name, arg->pull_vcard_listing.include_app_param, &arg->pull_vcard_listing.app_param); + break; + case BTC_PBA_CLIENT_PULL_VCARD_ENTRY_EVT: + ret = btc_pba_client_pull_vcard_entry(arg->pull_vcard_entry.handle, arg->pull_vcard_entry.name, arg->pull_vcard_entry.include_app_param, &arg->pull_vcard_entry.app_param); + break; + default: + BTC_TRACE_WARNING("unknown pba client action %i", msg->act); + break; + } + + if (!ret) { + /* operation failed, do deep free */ + btc_pba_client_args_deep_free(msg); + } +} + +static void parse_pull_phone_book_app_param(esp_pbac_param_t *param, uint8_t *app_param, uint16_t app_param_len) +{ + if (app_param == NULL || app_param_len == 0) { + return; + } + uint8_t *ptr = app_param; + while(ptr < app_param + app_param_len) { + switch (*ptr) + { + case BTA_PBAP_APP_PARAM_PHONE_BOOK_SIZE: + param->pull_phone_book_rsp.include_phone_book_size = 1; + ptr += BTA_PBAP_APP_PARAM_HEADER_LENGTH; + BE_STREAM_TO_UINT16(param->pull_phone_book_rsp.phone_book_size, ptr); + break; + case BTA_PBAP_APP_PARAM_NEW_MISSED_CALLS: + param->pull_phone_book_rsp.include_new_missed_calls = 1; + ptr += BTA_PBAP_APP_PARAM_HEADER_LENGTH; + BE_STREAM_TO_UINT8(param->pull_phone_book_rsp.new_missed_calls, ptr); + break; + case BTA_PBAP_APP_PARAM_PRIMARY_FOLDER_VERSION: + param->pull_phone_book_rsp.include_primary_folder_version = 1; + ptr += BTA_PBAP_APP_PARAM_HEADER_LENGTH; + /* don't copy */ + param->pull_phone_book_rsp.primary_folder_version = ptr; + ptr += BTA_PBAP_APP_PARAM_LENGTH_PRIMARY_FOLDER_VERSION; + break; + case BTA_PBAP_APP_PARAM_SECONDARY_FOLDER_VERSION: + param->pull_phone_book_rsp.include_secondary_folder_version = 1; + ptr += BTA_PBAP_APP_PARAM_HEADER_LENGTH; + /* don't copy */ + param->pull_phone_book_rsp.secondary_folder_version = ptr; + ptr += BTA_PBAP_APP_PARAM_LENGTH_SECONDARY_FOLDER_VERSION; + break; + case BTA_PBAP_APP_PARAM_DATABASE_IDENTIFIER: + param->pull_phone_book_rsp.include_database_identifier = 1; + ptr += BTA_PBAP_APP_PARAM_HEADER_LENGTH; + /* don't copy */ + param->pull_phone_book_rsp.database_identifier = ptr; + ptr += BTA_PBAP_APP_PARAM_LENGTH_DATABASE_IDENTIFIER; + break; + default: + goto error; + break; + } + } +error: + return; +} + +static void parse_pull_vcard_listing_app_param(esp_pbac_param_t *param, uint8_t *app_param, uint16_t app_param_len) +{ + if (app_param == NULL || app_param_len == 0) { + return; + } + uint8_t *ptr = app_param; + while(ptr < app_param + app_param_len) { + switch (*ptr) + { + case BTA_PBAP_APP_PARAM_PHONE_BOOK_SIZE: + param->pull_vcard_listing_rsp.include_phone_book_size = 1; + ptr += BTA_PBAP_APP_PARAM_HEADER_LENGTH; + BE_STREAM_TO_UINT16(param->pull_vcard_listing_rsp.phone_book_size, ptr); + break; + case BTA_PBAP_APP_PARAM_NEW_MISSED_CALLS: + param->pull_vcard_listing_rsp.include_new_missed_calls = 1; + ptr += BTA_PBAP_APP_PARAM_HEADER_LENGTH; + BE_STREAM_TO_UINT8(param->pull_vcard_listing_rsp.new_missed_calls, ptr); + break; + case BTA_PBAP_APP_PARAM_PRIMARY_FOLDER_VERSION: + param->pull_vcard_listing_rsp.include_primary_folder_version = 1; + ptr += BTA_PBAP_APP_PARAM_HEADER_LENGTH; + /* don't copy */ + param->pull_vcard_listing_rsp.primary_folder_version = ptr; + ptr += BTA_PBAP_APP_PARAM_LENGTH_PRIMARY_FOLDER_VERSION; + break; + case BTA_PBAP_APP_PARAM_SECONDARY_FOLDER_VERSION: + param->pull_vcard_listing_rsp.include_secondary_folder_version = 1; + ptr += BTA_PBAP_APP_PARAM_HEADER_LENGTH; + /* don't copy */ + param->pull_vcard_listing_rsp.secondary_folder_version = ptr; + ptr += BTA_PBAP_APP_PARAM_LENGTH_SECONDARY_FOLDER_VERSION; + break; + case BTA_PBAP_APP_PARAM_DATABASE_IDENTIFIER: + param->pull_vcard_listing_rsp.include_database_identifier = 1; + ptr += BTA_PBAP_APP_PARAM_HEADER_LENGTH; + /* don't copy */ + param->pull_vcard_listing_rsp.database_identifier = ptr; + ptr += BTA_PBAP_APP_PARAM_LENGTH_DATABASE_IDENTIFIER; + break; + default: + goto error; + break; + } + } +error: + return; +} + +static void parse_pull_vcard_entry_app_param(esp_pbac_param_t *param, uint8_t *app_param, uint16_t app_param_len) +{ + if (app_param == NULL || app_param_len == 0) { + return; + } + uint8_t *ptr = app_param; + while(ptr < app_param + app_param_len) { + switch (*ptr) + { + case BTA_PBAP_APP_PARAM_DATABASE_IDENTIFIER: + param->pull_vcard_entry_rsp.include_database_identifier = 1; + ptr += BTA_PBAP_APP_PARAM_HEADER_LENGTH; + /* don't copy */ + param->pull_vcard_entry_rsp.database_identifier = ptr; + ptr += BTA_PBAP_APP_PARAM_LENGTH_DATABASE_IDENTIFIER; + break; + default: + goto error; + break; + } + } +error: + return; +} + +static uint16_t get_next_dir_len_from_path(char *path, uint16_t path_len, uint16_t path_pos) +{ + uint16_t ret = 0; + for (int i = path_pos; i < path_len; ++i) { + if (path[i] == '/' || path[i] == '\0') { + break; + } + else { + ++ret; + } + } + return ret; +} + +void btc_pba_client_cb_handler(btc_msg_t *msg) +{ + uint16_t event = msg->act; + tBTA_PBA_CLIENT *p_data = (tBTA_PBA_CLIENT *)msg->arg; + btc_pba_client_ccb_t *p_ccb = NULL; + esp_pbac_param_t param = {0}; + + switch (event) { + case BTA_PBA_CLIENT_CONN_OPEN_EVT: + if (p_data->conn.error == BTA_PBA_CLIENT_NO_ERROR) { + /* allocate ccb */ + p_ccb = &btc_pba_client_cb.ccb[p_data->conn.handle - 1]; + p_ccb->handle = p_data->conn.handle; + bdcpy(p_ccb->bd_addr.address, p_data->conn.bd_addr); + p_ccb->busy = false; + param.conn_stat.connected = true; + param.conn_stat.peer_supported_repo = p_data->conn.peer_supported_repo; + param.conn_stat.peer_supported_feat = p_data->conn.peer_supported_feat; + param.conn_stat.reason = BTA_PBA_CLIENT_NO_ERROR; + } + else { + param.conn_stat.connected = false; + /* error codes are compatible */ + param.conn_stat.reason = p_data->conn.error; + } + bdcpy(param.conn_stat.remote_bda, p_data->conn.bd_addr); + param.conn_stat.handle = p_data->conn.handle; + btc_pba_client_callback_to_app(ESP_PBAC_CONNECTION_STATE_EVT, ¶m); + btc_queue_advance(); + break; + case BTA_PBA_CLIENT_CONN_CLOSE_EVT: + /* clear ccb */ + p_ccb = &btc_pba_client_cb.ccb[p_data->conn.handle - 1]; + p_ccb->handle = 0; + p_ccb->busy = false; + p_ccb->path_len = 0; + p_ccb->path_pos = 0; + if (p_ccb->path) { + osi_free(p_ccb->path); + p_ccb->path = NULL; + } + memset(p_ccb->bd_addr.address, 0, BD_ADDR_LEN); + param.conn_stat.connected = false; + bdcpy(param.conn_stat.remote_bda, p_data->conn.bd_addr); + param.conn_stat.handle = p_data->conn.handle; + /* error codes are compatible */ + param.conn_stat.reason = p_data->conn.error; + btc_pba_client_callback_to_app(ESP_PBAC_CONNECTION_STATE_EVT, ¶m); + btc_queue_advance(); + break; + case BTA_PBA_CLIENT_PULL_PHONE_BOOK_RSP_EVT: + if (p_data->response.final) { + p_ccb = &btc_pba_client_cb.ccb[p_data->response.handle - 1]; + p_ccb->busy = false; + } + param.pull_phone_book_rsp.handle = p_data->response.handle; + param.pull_phone_book_rsp.final = p_data->response.final; + param.pull_phone_book_rsp.result = p_data->response.status; + param.pull_phone_book_rsp.data = p_data->response.data; + param.pull_phone_book_rsp.data_len = p_data->response.data_len; + parse_pull_phone_book_app_param(¶m, p_data->response.app_param, p_data->response.app_param_len); + btc_pba_client_callback_to_app(ESP_PBAC_PULL_PHONE_BOOK_RESPONSE_EVT, ¶m); + if (p_data->response.pkt != NULL) { + osi_free(p_data->response.pkt); + } + break; + case BTA_PBA_CLIENT_SET_PHONE_BOOK_RSP_EVT: + p_ccb = &btc_pba_client_cb.ccb[p_data->response.handle - 1]; + if (p_data->response.status == BTA_PBA_CLIENT_NO_ERROR && p_ccb->path_pos < p_ccb->path_len) { + /* since path_len is not zero, path should not be NULL, use asset to check */ + assert(p_ccb->path != NULL); + uint16_t dir_name_len = get_next_dir_len_from_path(p_ccb->path, p_ccb->path_len, p_ccb->path_pos); + if (dir_name_len > 0) { + char *dir_name = osi_malloc(dir_name_len + 1); + assert(dir_name != NULL); + memcpy(dir_name, p_ccb->path + p_ccb->path_pos, dir_name_len); + dir_name[dir_name_len] = '\0'; + p_ccb->path_pos += dir_name_len + 1; + BTA_PbaClientSetPhoneBook(p_data->response.handle, ESP_PBAC_SET_PHONE_BOOK_FLAGS_DOWN, dir_name); + /* break here, don't report event to upper */ + break; + } + } + /* set path done or failed, clear status */ + p_ccb->path_len = 0; + p_ccb->path_pos = 0; + if (p_ccb->path) { + osi_free(p_ccb->path); + p_ccb->path = NULL; + } + p_ccb->busy = false; + param.set_phone_book_rsp.handle = p_data->response.handle; + param.set_phone_book_rsp.result = p_data->response.status; + btc_pba_client_callback_to_app(ESP_PBAC_SET_PHONE_BOOK_RESPONSE_EVT, ¶m); + if (p_data->response.pkt != NULL) { + osi_free(p_data->response.pkt); + } + break; + case BTA_PBA_CLIENT_PULL_VCARD_LISTING_RSP_EVT: + if (p_data->response.final) { + p_ccb = &btc_pba_client_cb.ccb[p_data->response.handle - 1]; + p_ccb->busy = false; + } + param.pull_vcard_listing_rsp.handle = p_data->response.handle; + param.pull_vcard_listing_rsp.final = p_data->response.final; + param.pull_vcard_listing_rsp.result = p_data->response.status; + param.pull_vcard_listing_rsp.data = p_data->response.data; + param.pull_vcard_listing_rsp.data_len = p_data->response.data_len; + parse_pull_vcard_listing_app_param(¶m, p_data->response.app_param, p_data->response.app_param_len); + btc_pba_client_callback_to_app(ESP_PBAC_PULL_VCARD_LISTING_RESPONSE_EVT, ¶m); + if (p_data->response.pkt != NULL) { + osi_free(p_data->response.pkt); + } + break; + case BTA_PBA_CLIENT_PULL_VCARD_ENTRY_RSP_EVT: + if (p_data->response.final) { + p_ccb = &btc_pba_client_cb.ccb[p_data->response.handle - 1]; + p_ccb->busy = false; + } + param.pull_vcard_entry_rsp.handle = p_data->response.handle; + param.pull_vcard_entry_rsp.final = p_data->response.final; + param.pull_vcard_entry_rsp.result = p_data->response.status; + param.pull_vcard_entry_rsp.data = p_data->response.data; + param.pull_vcard_entry_rsp.data_len = p_data->response.data_len; + parse_pull_vcard_entry_app_param(¶m, p_data->response.app_param, p_data->response.app_param_len); + btc_pba_client_callback_to_app(ESP_PBAC_PULL_VCARD_ENTRY_RESPONSE_EVT, ¶m); + if (p_data->response.pkt != NULL) { + osi_free(p_data->response.pkt); + } + break; + case BTA_PBA_CLIENT_REGISTER_EVT: + /* init process: Enable -> Register */ + btc_pba_client_callback_to_app(ESP_PBAC_INIT_EVT, NULL); + break; + case BTA_PBA_CLIENT_DISABLE_EVT: + /* deinit process: Deregister -> Disable */ + btc_pba_client_callback_to_app(ESP_PBAC_DEINIT_EVT, NULL); + break; + default: + BTC_TRACE_WARNING("%s: unknown event (%d)", __func__, event); + break; + } +} + +#endif diff --git a/components/bt/host/bluedroid/common/include/common/bluedroid_user_config.h b/components/bt/host/bluedroid/common/include/common/bluedroid_user_config.h index 482c1c9a457e..9cf478c4f617 100644 --- a/components/bt/host/bluedroid/common/include/common/bluedroid_user_config.h +++ b/components/bt/host/bluedroid/common/include/common/bluedroid_user_config.h @@ -124,6 +124,25 @@ #define UC_BT_ENC_KEY_SIZE_CTRL_MODE 0 #endif +//PBAP Client +#ifdef CONFIG_BT_PBAC_ENABLED +#define UC_BT_PBAC_ENABLED CONFIG_BT_PBAC_ENABLED +#else +#define UC_BT_PBAC_ENABLED FALSE +#endif + +#ifdef CONFIG_BT_PBAC_SUPPORTED_FEAT +#define UC_BT_PBAC_SUPPORTED_FEAT CONFIG_BT_PBAC_SUPPORTED_FEAT +#else +#define UC_BT_PBAC_SUPPORTED_FEAT 0x00 +#endif + +#ifdef CONFIG_BT_PBAC_PREFERRED_MTU +#define UC_BT_PBAC_PREFERRED_MTU CONFIG_BT_PBAC_PREFERRED_MTU +#else +#define UC_BT_PBAC_PREFERRED_MTU 0 +#endif + //GOEPC (BT) #ifdef CONFIG_BT_GOEPC_ENABLED #define UC_BT_GOEPC_ENABLED CONFIG_BT_GOEPC_ENABLED diff --git a/components/bt/host/bluedroid/common/include/common/bt_target.h b/components/bt/host/bluedroid/common/include/common/bt_target.h index 0e869dad45a1..239433620b26 100644 --- a/components/bt/host/bluedroid/common/include/common/bt_target.h +++ b/components/bt/host/bluedroid/common/include/common/bt_target.h @@ -177,12 +177,22 @@ #endif /* UC_BT_HID_DEVICE_ENABLED */ #if UC_BT_GOEPC_ENABLED +#ifndef RFCOMM_INCLUDED +#define RFCOMM_INCLUDED TRUE +#endif #ifndef OBEX_INCLUDED #define OBEX_INCLUDED TRUE #endif #define GOEPC_INCLUDED TRUE #endif /* UC_BT_GOEPC_ENABLED */ +#if UC_BT_PBAC_ENABLED +#define BTC_PBA_CLIENT_INCLUDED TRUE +#define BTC_PBA_SUPPORTED_FEAT UC_BT_PBAC_SUPPORTED_FEAT +#define BTC_PBA_PREFERRED_MTU UC_BT_PBAC_PREFERRED_MTU +#define BTA_PBA_CLIENT_INCLUDED TRUE +#endif + #endif /* UC_BT_CLASSIC_ENABLED */ /* This is set to enable use of GAP L2CAP connections. */ diff --git a/components/bt/host/bluedroid/common/include/common/bt_trace.h b/components/bt/host/bluedroid/common/include/common/bt_trace.h index 74b7f10574d7..9acaf69097af 100644 --- a/components/bt/host/bluedroid/common/include/common/bt_trace.h +++ b/components/bt/host/bluedroid/common/include/common/bt_trace.h @@ -330,6 +330,13 @@ static inline void trc_dump_buffer(const char *prefix, uint8_t *data, uint16_t l #define OBEX_TL_L2CAP_TRACE_EVENT(fmt, args...) {if (obex_tl_l2cap_cb.trace_level >= BT_TRACE_LEVEL_EVENT && BT_LOG_LEVEL_CHECK(AVRC,EVENT)) BT_PRINT_D("OBEX_TL_L2CAP", fmt, ## args);} #define OBEX_TL_L2CAP_TRACE_DEBUG(fmt, args...) {if (obex_tl_l2cap_cb.trace_level >= BT_TRACE_LEVEL_DEBUG && BT_LOG_LEVEL_CHECK(AVRC,DEBUG)) BT_PRINT_D("OBEX_TL_L2CAP", fmt, ## args);} +/* Define tracing for OBEX_TL_RFCOMM */ +#define OBEX_TL_RFCOMM_TRACE_ERROR(fmt, args...) {if (obex_tl_rfcomm_cb.trace_level >= BT_TRACE_LEVEL_ERROR && BT_LOG_LEVEL_CHECK(AVRC, ERROR)) BT_PRINT_E("OBEX_TL_RFCOMM", fmt, ## args);} +#define OBEX_TL_RFCOMM_TRACE_WARNING(fmt, args...) {if (obex_tl_rfcomm_cb.trace_level >= BT_TRACE_LEVEL_WARNING && BT_LOG_LEVEL_CHECK(AVRC, WARNING)) BT_PRINT_W("OBEX_TL_RFCOMM", fmt, ## args);} +#define OBEX_TL_RFCOMM_TRACE_API(fmt, args...) {if (obex_tl_rfcomm_cb.trace_level >= BT_TRACE_LEVEL_API && BT_LOG_LEVEL_CHECK(AVRC,API)) BT_PRINT_I("OBEX_TL_RFCOMM", fmt, ## args);} +#define OBEX_TL_RFCOMM_TRACE_EVENT(fmt, args...) {if (obex_tl_rfcomm_cb.trace_level >= BT_TRACE_LEVEL_EVENT && BT_LOG_LEVEL_CHECK(AVRC,EVENT)) BT_PRINT_D("OBEX_TL_RFCOMM", fmt, ## args);} +#define OBEX_TL_RFCOMM_TRACE_DEBUG(fmt, args...) {if (obex_tl_rfcomm_cb.trace_level >= BT_TRACE_LEVEL_DEBUG && BT_LOG_LEVEL_CHECK(AVRC,DEBUG)) BT_PRINT_D("OBEX_TL_RFCOMM", fmt, ## args);} + /* Define tracing for GOEPC */ #define GOEPC_TRACE_ERROR(fmt, args...) {if (goepc_cb.trace_level >= BT_TRACE_LEVEL_ERROR && BT_LOG_LEVEL_CHECK(AVRC, ERROR)) BT_PRINT_E("BT_GOEPC", fmt, ## args);} #define GOEPC_TRACE_WARNING(fmt, args...) {if (goepc_cb.trace_level >= BT_TRACE_LEVEL_WARNING && BT_LOG_LEVEL_CHECK(AVRC, WARNING)) BT_PRINT_W("BT_GOEPC", fmt, ## args);} @@ -512,12 +519,20 @@ extern UINT8 btif_trace_level; #define OBEX_TRACE_EVENT(fmt, args...) #define OBEX_TRACE_DEBUG(fmt, args...) +/* Define tracing for OBEX L2CAP transport layer */ #define OBEX_TL_L2CAP_TRACE_ERROR(fmt, args...) #define OBEX_TL_L2CAP_TRACE_WARNING(fmt, args...) #define OBEX_TL_L2CAP_TRACE_API(fmt, args...) #define OBEX_TL_L2CAP_TRACE_EVENT(fmt, args...) #define OBEX_TL_L2CAP_TRACE_DEBUG(fmt, args...) +/* Define tracing for OBEX RFCOMM transport layer */ +#define OBEX_TL_RFCOMM_TRACE_ERROR(fmt, args...) +#define OBEX_TL_RFCOMM_TRACE_WARNING(fmt, args...) +#define OBEX_TL_RFCOMM_TRACE_API(fmt, args...) +#define OBEX_TL_RFCOMM_TRACE_EVENT(fmt, args...) +#define OBEX_TL_RFCOMM_TRACE_DEBUG(fmt, args...) + /* Define tracing for GOEPC */ #define GOEPC_TRACE_ERROR(fmt, args...) #define GOEPC_TRACE_WARNING(fmt, args...) diff --git a/components/bt/host/bluedroid/main/bte_init.c b/components/bt/host/bluedroid/main/bte_init.c index 77508b37773e..c60f6f7bd173 100644 --- a/components/bt/host/bluedroid/main/bte_init.c +++ b/components/bt/host/bluedroid/main/bte_init.c @@ -156,6 +156,10 @@ #include "bta_pan_int.h" #endif +#if BTA_PBA_CLIENT_INCLUDED == TRUE +#include "bta_pba_client_int.h" +#endif + #include "bta_sys_int.h" // control block for patch ram downloading @@ -184,6 +188,12 @@ void BTE_DeinitStack(void) { //BTA Modules #if (BTA_INCLUDED == TRUE && BTA_DYNAMIC_MEMORY == TRUE) +#if BTA_PBA_CLIENT_INCLUDED == TRUE + if (bta_pba_client_cb_ptr) { + osi_free(bta_pba_client_cb_ptr); + bta_pba_client_cb_ptr = NULL; + } +#endif #if GATTS_INCLUDED == TRUE if (bta_gatts_cb_ptr){ osi_free(bta_gatts_cb_ptr); @@ -521,6 +531,12 @@ bt_status_t BTE_InitStack(void) #if BTA_PAN_INCLUDED==TRUE memset((void *)bta_pan_cb_ptr, 0, sizeof(tBTA_PAN_CB)); #endif +#if BTA_PBA_CLIENT_INCLUDED == TRUE + if ((bta_pba_client_cb_ptr = (tBTA_PBA_CLIENT_CB *)osi_malloc(sizeof(tBTA_PBA_CLIENT_CB))) == NULL) { + goto error_exit; + } + memset((void *)bta_pba_client_cb_ptr, 0, sizeof(tBTA_PBA_CLIENT_CB)); +#endif #endif // BTA_INCLUDED == TRUE return BT_STATUS_SUCCESS; diff --git a/components/bt/host/bluedroid/stack/goep/goepc_api.c b/components/bt/host/bluedroid/stack/goep/goepc_api.c index 60badbaeec0c..350e80c9c27d 100644 --- a/components/bt/host/bluedroid/stack/goep/goepc_api.c +++ b/components/bt/host/bluedroid/stack/goep/goepc_api.c @@ -359,7 +359,7 @@ UINT16 GOEPC_RequestAddHeader(UINT16 handle, UINT8 header_id, const UINT8 *data, break; } - if (data == NULL || data_len == 0) { + if ((data == NULL) ^ (data_len == 0)) { ret = GOEP_INVALID_PARAM; break; } diff --git a/components/bt/host/bluedroid/stack/include/stack/goep_common.h b/components/bt/host/bluedroid/stack/include/stack/goep_common.h index 82b315691e8d..74cb3ca8db5f 100644 --- a/components/bt/host/bluedroid/stack/include/stack/goep_common.h +++ b/components/bt/host/bluedroid/stack/include/stack/goep_common.h @@ -8,11 +8,12 @@ #include "common/bt_target.h" -#define GOEP_SUCCESS 0 /* Operation successful */ -#define GOEP_FAILURE 1 /* Operation failed */ -#define GOEP_NO_RESOURCES 3 /* Not enough resources */ -#define GOEP_BAD_HANDLE 4 /* Bad handle */ -#define GOEP_INVALID_PARAM 5 /* Invalid parameter */ -#define GOEP_INVALID_STATE 6 /* Operation not allow in current state */ -#define GOEP_CONGEST 7 /* Congest */ -#define GOEP_TL_ERROR 8 /* Lower transport layer error */ +/* GOEP Client or Server(not supported yet) API return code */ +#define GOEP_SUCCESS 0x00 /* Operation successful */ +#define GOEP_FAILURE 0x01 /* Operation failed */ +#define GOEP_NO_RESOURCES 0x02 /* Not enough resources */ +#define GOEP_BAD_HANDLE 0x04 /* Bad handle */ +#define GOEP_INVALID_PARAM 0x08 /* Invalid parameter */ +#define GOEP_INVALID_STATE 0x10 /* Operation not allow in current state */ +#define GOEP_CONGEST 0x20 /* Congest */ +#define GOEP_TL_ERROR 0x40 /* Lower transport layer error */ diff --git a/components/bt/host/bluedroid/stack/include/stack/obex_api.h b/components/bt/host/bluedroid/stack/include/stack/obex_api.h index e130a48734ed..d22cd33c527f 100644 --- a/components/bt/host/bluedroid/stack/include/stack/obex_api.h +++ b/components/bt/host/bluedroid/stack/include/stack/obex_api.h @@ -19,8 +19,6 @@ #define OBEX_NOT_OPEN 6 /* Connection not open */ #define OBEX_PACKET_TOO_LARGE 7 /* Packet size large than MTU */ #define OBEX_ERROR_TL 8 /* Operation failed in transport layer */ -#define OBEX_TRY_AGAIN 9 /* Operation failed, connection congestion, try again */ - /* * OBEX profile definitions @@ -162,12 +160,21 @@ typedef struct BD_ADDR addr; /* peer bluetooth device address */ } tOBEX_OVER_L2CAP_SVR; +typedef struct +{ + UINT8 scn; /* service channel number */ + UINT16 sec_mask; /* security mask */ + UINT16 pref_mtu; /* preferred mtu, limited by rfcomm mtu */ + BD_ADDR addr; /* peer bluetooth device address */ +} tOBEX_OVER_RFCOMM_SVR; + typedef struct { UINT8 tl; /* transport type, OBEX_OVER_L2CAP or OBEX_OVER_RFCOMM */ union { tOBEX_OVER_L2CAP_SVR l2cap; + tOBEX_OVER_RFCOMM_SVR rfcomm; }; } tOBEX_SVR_INFO; diff --git a/components/bt/host/bluedroid/stack/obex/include/obex_int.h b/components/bt/host/bluedroid/stack/obex/include/obex_int.h index 4512e89c2a7f..28c4eab45d5d 100644 --- a/components/bt/host/bluedroid/stack/obex/include/obex_int.h +++ b/components/bt/host/bluedroid/stack/obex/include/obex_int.h @@ -14,7 +14,13 @@ #if (OBEX_INCLUDED == TRUE) -#define OBEX_BT_HDR_MIN_OFFSET OBEX_TL_L2CAP_BT_HDR_MIN_OFFSET /* should set to max value of all transport layer */ +#if (RFCOMM_INCLUDED == TRUE) +#define OBEX_BT_HDR_MIN_OFFSET OBEX_TL_RFCOMM_BT_HDR_MIN_OFFSET /* should set to max value of all transport layer */ +#define OBEX_BT_HDR_RESERVE_LEN OBEX_TL_RFCOMM_BT_HDR_RESERVE_LEN /* should set to max value of all transport layer */ +#else +#define OBEX_BT_HDR_MIN_OFFSET OBEX_TL_L2CAP_BT_HDR_OFFSET_MIN +#define OBEX_BT_HDR_RESERVE_LEN OBEX_TL_L2CAP_BT_HDR_RESERVE_LEN +#endif #define OBEX_ROLE_CLIENT 0x01 #define OBEX_ROLE_SERVER 0x02 @@ -24,10 +30,6 @@ #define OBEX_STATE_OPENING 1 /* Starting to open a connection */ #define OBEX_STATE_OPENED 2 /* Connection opened */ -/* Store 16 bits data in big endian format, not modify the p_buf */ -#define STORE16BE(p_buf, data) do { *p_buf = ((data)>>8)&0xff; \ - *(p_buf+1) = (data)&0xff;} while(0) - /* OBEX Connection Control block */ typedef struct { tOBEX_MSG_CBACK *callback; /* Connection msg callback function */ @@ -65,6 +67,7 @@ extern tOBEX_CB *obex_cb_ptr; #endif void obex_tl_l2cap_callback(tOBEX_TL_EVT evt, tOBEX_TL_MSG *msg); +void obex_tl_rfcomm_callback(tOBEX_TL_EVT evt, tOBEX_TL_MSG *msg); tOBEX_CCB *obex_allocate_ccb(void); tOBEX_SCB *obex_allocate_scb(void); void obex_free_ccb(tOBEX_CCB *p_ccb); diff --git a/components/bt/host/bluedroid/stack/obex/include/obex_tl.h b/components/bt/host/bluedroid/stack/obex/include/obex_tl.h index 6a60ed51dc61..9211b9b0c207 100644 --- a/components/bt/host/bluedroid/stack/obex/include/obex_tl.h +++ b/components/bt/host/bluedroid/stack/obex/include/obex_tl.h @@ -68,9 +68,18 @@ typedef struct BD_ADDR addr; /* peer bluetooth device address */ } tOBEX_TL_L2CAP_SVR; +typedef struct +{ + UINT8 scn; /* service channel number */ + UINT16 sec_mask; /* security mask */ + UINT16 pref_mtu; /* preferred mtu, limited by rfcomm mtu */ + BD_ADDR addr; /* peer bluetooth device address */ +} tOBEX_TL_RFCOMM_SVR; + typedef union { tOBEX_TL_L2CAP_SVR l2cap; + tOBEX_TL_RFCOMM_SVR rfcomm; } tOBEX_TL_SVR_INFO; typedef void (tOBEX_TL_CBACK)(tOBEX_TL_EVT evt, tOBEX_TL_MSG *msg); diff --git a/components/bt/host/bluedroid/stack/obex/include/obex_tl_l2cap.h b/components/bt/host/bluedroid/stack/obex/include/obex_tl_l2cap.h index 83f062bb6966..326677665a1a 100644 --- a/components/bt/host/bluedroid/stack/obex/include/obex_tl_l2cap.h +++ b/components/bt/host/bluedroid/stack/obex/include/obex_tl_l2cap.h @@ -10,7 +10,8 @@ #if (OBEX_INCLUDED == TRUE) -#define OBEX_TL_L2CAP_BT_HDR_MIN_OFFSET 13 /* L2CAP_MIN_OFFSET */ +#define OBEX_TL_L2CAP_BT_HDR_MIN_OFFSET 13 /* equal to L2CAP_MIN_OFFSET */ +#define OBEX_TL_L2CAP_BT_HDR_RESERVE_LEN 0 /* not require any additional byte */ tOBEX_TL_OPS *obex_tl_l2cap_ops_get(void); diff --git a/components/bt/host/bluedroid/stack/obex/include/obex_tl_rfcomm.h b/components/bt/host/bluedroid/stack/obex/include/obex_tl_rfcomm.h new file mode 100644 index 000000000000..61b9d2958999 --- /dev/null +++ b/components/bt/host/bluedroid/stack/obex/include/obex_tl_rfcomm.h @@ -0,0 +1,18 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "obex_tl.h" + +#if (OBEX_INCLUDED == TRUE && RFCOMM_INCLUDED == TRUE) + +#define OBEX_TL_RFCOMM_BT_HDR_MIN_OFFSET 18 /* RFCOMM_MIN_OFFSET + L2CAP_MIN_OFFSET */ +#define OBEX_TL_RFCOMM_BT_HDR_RESERVE_LEN 1 /* reserve 1 byte for rfcomm fcs */ + +tOBEX_TL_OPS *obex_tl_rfcomm_ops_get(void); + +#endif /* #if (OBEX_INCLUDED == TRUE && RFCOMM_INCLUDED == TRUE) */ diff --git a/components/bt/host/bluedroid/stack/obex/obex_api.c b/components/bt/host/bluedroid/stack/obex/obex_api.c index befabb0e92b3..8851a0f81a1a 100644 --- a/components/bt/host/bluedroid/stack/obex/obex_api.c +++ b/components/bt/host/bluedroid/stack/obex/obex_api.c @@ -14,6 +14,7 @@ #include "obex_int.h" #include "obex_tl.h" #include "obex_tl_l2cap.h" +#include "obex_tl_rfcomm.h" #if (OBEX_INCLUDED == TRUE) @@ -25,6 +26,12 @@ static inline void obex_server_to_tl_server(tOBEX_SVR_INFO *server, tOBEX_TL_SVR tl_server->l2cap.pref_mtu = server->l2cap.pref_mtu; bdcpy(tl_server->l2cap.addr, server->l2cap.addr); } + else if (server->tl == OBEX_OVER_RFCOMM) { + tl_server->rfcomm.scn = server->rfcomm.scn; + tl_server->rfcomm.sec_mask = server->rfcomm.sec_mask; + tl_server->rfcomm.pref_mtu = server->rfcomm.pref_mtu; + bdcpy(tl_server->rfcomm.addr, server->rfcomm.addr); + } else { OBEX_TRACE_ERROR("Unsupported OBEX transport type\n"); assert(0); @@ -34,7 +41,7 @@ static inline void obex_server_to_tl_server(tOBEX_SVR_INFO *server, tOBEX_TL_SVR static inline void obex_updata_packet_length(BT_HDR *p_buf, UINT16 len) { UINT8 *p_pkt_len = (UINT8 *)(p_buf + 1) + p_buf->offset + 1; - STORE16BE(p_pkt_len, len); + UINT16_TO_BE_FIELD(p_pkt_len, len); } /******************************************************************************* @@ -62,13 +69,12 @@ UINT16 OBEX_Init(void) if (obex_cb.tl_ops[OBEX_OVER_L2CAP]->init != NULL) { obex_cb.tl_ops[OBEX_OVER_L2CAP]->init(obex_tl_l2cap_callback); } - /* Not implement yet */ - /* +#if (RFCOMM_INCLUDED == TRUE) obex_cb.tl_ops[OBEX_OVER_RFCOMM] = obex_tl_rfcomm_ops_get(); if (obex_cb.tl_ops[OBEX_OVER_RFCOMM]->init != NULL) { obex_cb.tl_ops[OBEX_OVER_RFCOMM]->init(obex_tl_rfcomm_callback); } - */ +#endif obex_cb.trace_level = BT_TRACE_LEVEL_ERROR; return OBEX_SUCCESS; } @@ -317,7 +323,7 @@ UINT16 OBEX_BuildRequest(tOBEX_PARSE_INFO *info, UINT16 buff_size, BT_HDR **out_ if (buff_size < OBEX_MIN_PACKET_SIZE || info == NULL || out_pkt == NULL) { return OBEX_INVALID_PARAM; } - buff_size += sizeof(BT_HDR) + OBEX_BT_HDR_MIN_OFFSET; + buff_size += sizeof(BT_HDR) + OBEX_BT_HDR_MIN_OFFSET + OBEX_BT_HDR_RESERVE_LEN; BT_HDR *p_buf= (BT_HDR *)osi_malloc(buff_size); if (p_buf == NULL) { @@ -327,7 +333,7 @@ UINT16 OBEX_BuildRequest(tOBEX_PARSE_INFO *info, UINT16 buff_size, BT_HDR **out_ UINT16 pkt_len = OBEX_MIN_PACKET_SIZE; p_buf->offset = OBEX_BT_HDR_MIN_OFFSET; /* use layer_specific to store the max data length allowed */ - p_buf->layer_specific = buff_size - sizeof(BT_HDR) - OBEX_BT_HDR_MIN_OFFSET; + p_buf->layer_specific = buff_size - sizeof(BT_HDR) - OBEX_BT_HDR_MIN_OFFSET - OBEX_BT_HDR_RESERVE_LEN; UINT8 *p_data = (UINT8 *)(p_buf + 1) + p_buf->offset; /* byte 0: opcode */ *p_data++ = info->opcode; @@ -343,7 +349,7 @@ UINT16 OBEX_BuildRequest(tOBEX_PARSE_INFO *info, UINT16 buff_size, BT_HDR **out_ /* byte 4: flags */ *p_data++ = info->flags; /* byte 5, 6: maximum OBEX packet length, recommend to set as our mtu*/ - STORE16BE(p_data, info->max_packet_length); + UINT16_TO_BE_FIELD(p_data, info->max_packet_length); pkt_len += 4; break; case OBEX_OPCODE_SETPATH: @@ -357,7 +363,7 @@ UINT16 OBEX_BuildRequest(tOBEX_PARSE_INFO *info, UINT16 buff_size, BT_HDR **out_ break; } - STORE16BE(p_pkt_len, pkt_len); + UINT16_TO_BE_FIELD(p_pkt_len, pkt_len); p_buf->len = pkt_len; *out_pkt = p_buf; return OBEX_SUCCESS; @@ -378,7 +384,7 @@ UINT16 OBEX_BuildResponse(tOBEX_PARSE_INFO *info, UINT16 buff_size, BT_HDR **out if (buff_size < OBEX_MIN_PACKET_SIZE || info == NULL || out_pkt == NULL) { return OBEX_INVALID_PARAM; } - buff_size += sizeof(BT_HDR) + OBEX_BT_HDR_MIN_OFFSET; + buff_size += sizeof(BT_HDR) + OBEX_BT_HDR_MIN_OFFSET + OBEX_BT_HDR_RESERVE_LEN; BT_HDR *p_buf= (BT_HDR *)osi_malloc(buff_size); if (p_buf == NULL) { @@ -388,7 +394,7 @@ UINT16 OBEX_BuildResponse(tOBEX_PARSE_INFO *info, UINT16 buff_size, BT_HDR **out UINT16 pkt_len = OBEX_MIN_PACKET_SIZE; p_buf->offset = OBEX_BT_HDR_MIN_OFFSET; /* use layer_specific to store the max data length allowed */ - p_buf->layer_specific = buff_size - sizeof(BT_HDR) - OBEX_BT_HDR_MIN_OFFSET; + p_buf->layer_specific = buff_size - sizeof(BT_HDR) - OBEX_BT_HDR_MIN_OFFSET - OBEX_BT_HDR_RESERVE_LEN; UINT8 *p_data = (UINT8 *)(p_buf + 1) + p_buf->offset; /* byte 0: response code */ *p_data++ = info->response_code; @@ -405,14 +411,14 @@ UINT16 OBEX_BuildResponse(tOBEX_PARSE_INFO *info, UINT16 buff_size, BT_HDR **out /* byte 4: flags */ *p_data++ = info->flags; /* byte 5, 6: maximum OBEX packet length, recommend to set as our mtu */ - STORE16BE(p_data, info->max_packet_length); + UINT16_TO_BE_FIELD(p_data, info->max_packet_length); pkt_len += 4; break; default: break; } - STORE16BE(p_pkt_len, pkt_len); + UINT16_TO_BE_FIELD(p_pkt_len, pkt_len); p_buf->len = pkt_len; *out_pkt = p_buf; return OBEX_SUCCESS; @@ -465,7 +471,7 @@ UINT16 OBEX_AppendHeader(BT_HDR *pkt, const UINT8 *header) pkt->len += header_len; /* point to packet len */ p_data++; - STORE16BE(p_data, pkt->len); + UINT16_TO_BE_FIELD(p_data, pkt->len); return OBEX_SUCCESS; } @@ -481,7 +487,11 @@ UINT16 OBEX_AppendHeader(BT_HDR *pkt, const UINT8 *header) *******************************************************************************/ UINT16 OBEX_AppendHeaderRaw(BT_HDR *pkt, UINT8 header_id, const UINT8 *data, UINT16 data_len) { - if (pkt == NULL || data == NULL) { + if (pkt == NULL) { + return OBEX_INVALID_PARAM; + } + + if ((data == NULL) ^ (data_len == 0)) { return OBEX_INVALID_PARAM; } @@ -524,15 +534,17 @@ UINT16 OBEX_AppendHeaderRaw(BT_HDR *pkt, UINT8 header_id, const UINT8 *data, UIN *p_start++ = header_id; if (store_header_len) { /* store header length */ - STORE16BE(p_start, header_len); + UINT16_TO_BE_FIELD(p_start, header_len); p_start+= 2; } - /* store data */ - memcpy(p_start, data, data_len); + if (data != NULL) { + /* store data */ + memcpy(p_start, data, data_len); + } pkt->len += header_len; /* point to packet len */ p_data++; - STORE16BE(p_data, pkt->len); + UINT16_TO_BE_FIELD(p_data, pkt->len); return OBEX_SUCCESS; } diff --git a/components/bt/host/bluedroid/stack/obex/obex_main.c b/components/bt/host/bluedroid/stack/obex/obex_main.c index c5f04539021d..7810ff3c3324 100644 --- a/components/bt/host/bluedroid/stack/obex/obex_main.c +++ b/components/bt/host/bluedroid/stack/obex/obex_main.c @@ -208,4 +208,9 @@ void obex_tl_l2cap_callback(tOBEX_TL_EVT evt, tOBEX_TL_MSG *msg) obex_tl_evt_handler(OBEX_OVER_L2CAP, evt, msg); } +void obex_tl_rfcomm_callback(tOBEX_TL_EVT evt, tOBEX_TL_MSG *msg) +{ + obex_tl_evt_handler(OBEX_OVER_RFCOMM, evt, msg); +} + #endif /* #if (OBEX_INCLUDED == TRUE) */ diff --git a/components/bt/host/bluedroid/stack/obex/obex_tl_l2cap.c b/components/bt/host/bluedroid/stack/obex/obex_tl_l2cap.c index dfd423c73d9a..d509b4cfabf7 100644 --- a/components/bt/host/bluedroid/stack/obex/obex_tl_l2cap.c +++ b/components/bt/host/bluedroid/stack/obex/obex_tl_l2cap.c @@ -484,6 +484,10 @@ void obex_tl_l2cap_disconnect_ind(UINT16 lcid, BOOLEAN is_conf_needed) return; } + if (p_ccb->initiator && find_scb_by_psm(p_ccb->vpsm) == NULL) { + L2CA_Deregister(p_ccb->vpsm); + } + tOBEX_TL_MSG msg = {0}; msg.any.hdl = p_ccb->allocated; obex_tl_l2cap_cb.callback(OBEX_TL_DIS_CONN_EVT, &msg); diff --git a/components/bt/host/bluedroid/stack/obex/obex_tl_rfcomm.c b/components/bt/host/bluedroid/stack/obex/obex_tl_rfcomm.c new file mode 100644 index 000000000000..ce7799c8673f --- /dev/null +++ b/components/bt/host/bluedroid/stack/obex/obex_tl_rfcomm.c @@ -0,0 +1,439 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include "osi/osi.h" +#include "osi/allocator.h" +#include "common/bt_target.h" + +#include "stack/port_api.h" +#include "stack/btm_api.h" +#include "stack/sdpdefs.h" +#include "obex_tl.h" +#include "obex_tl_rfcomm.h" + +#if (OBEX_INCLUDED == TRUE && RFCOMM_INCLUDED == TRUE) + +#define OBEX_TL_RFCOMM_NUM_CONN 4 +#define OBEX_TL_RFCOMM_NUM_SERVER 2 + +#define OBEX_TL_RFCOMM_EVENT_MARK (PORT_EV_FC | PORT_EV_FCS) + +typedef struct { + UINT16 rfc_handle; /* rfcomm handle */ + UINT16 mtu; /* rfcomm mtu */ + BOOLEAN initiator; /* TRUE if is initiator, otherwise FALSE */ + UINT8 scn; /* service channel number */ + BD_ADDR addr; /* peer bluetooth device address */ + UINT8 allocated; /* 0 if not allocated, otherwise, index + 1, equal to handle */ +} tOBEX_TL_RFCOMM_CCB; + +typedef struct { + UINT16 rfc_handle; /* rfcomm handle */ + UINT8 scn; /* service channel number */ + UINT8 allocated; /* 0 if not allocated, otherwise, index + 1, handle of server will left shift 8 bits */ +} tOBEX_TL_RFCOMM_SCB; + +typedef struct { + tOBEX_TL_CBACK *callback; /* Upper layer callback */ + tOBEX_TL_RFCOMM_CCB ccb[OBEX_TL_RFCOMM_NUM_CONN]; + tOBEX_TL_RFCOMM_SCB scb[OBEX_TL_RFCOMM_NUM_SERVER]; + UINT8 trace_level; /* trace level */ +} tOBEX_TL_RFCOMM_CB; + +#if OBEX_DYNAMIC_MEMORY == FALSE +static tOBEX_TL_RFCOMM_CB obex_tl_rfcomm_cb; +#else +static tOBEX_TL_RFCOMM_CB *obex_tl_rfcomm_cb_ptr = NULL; +#define obex_tl_rfcomm_cb (*obex_tl_rfcomm_cb_ptr) +#endif + +static tOBEX_TL_RFCOMM_CCB *allocate_ccb(void) +{ + tOBEX_TL_RFCOMM_CCB *p_ccb = NULL; + for(int i = 0; i < OBEX_TL_RFCOMM_NUM_CONN; ++i) { + if (obex_tl_rfcomm_cb.ccb[i].allocated == 0) { + obex_tl_rfcomm_cb.ccb[i].allocated = i + 1; + p_ccb = &obex_tl_rfcomm_cb.ccb[i]; + break; + } + } + return p_ccb; +} + +static tOBEX_TL_RFCOMM_SCB *allocate_scb(void) +{ + tOBEX_TL_RFCOMM_SCB *p_scb = NULL; + for(int i = 0; i < OBEX_TL_RFCOMM_NUM_SERVER; ++i) { + if (obex_tl_rfcomm_cb.scb[i].allocated == 0) { + obex_tl_rfcomm_cb.scb[i].allocated = i + 1; + p_scb = &obex_tl_rfcomm_cb.scb[i]; + break; + } + } + return p_scb; +} + +static void free_ccb(tOBEX_TL_RFCOMM_CCB *p_ccb) +{ + memset(p_ccb, 0, sizeof(tOBEX_TL_RFCOMM_CCB)); +} + +static void free_scb(tOBEX_TL_RFCOMM_SCB *p_scb) +{ + memset(p_scb, 0, sizeof(tOBEX_TL_RFCOMM_SCB)); +} + +static tOBEX_TL_RFCOMM_CCB *find_ccb_by_handle(UINT16 handle) +{ + tOBEX_TL_RFCOMM_CCB *p_ccb = NULL; + if (handle > 0 && handle <= OBEX_TL_RFCOMM_NUM_CONN) { + if (obex_tl_rfcomm_cb.ccb[handle-1].allocated == handle) { + p_ccb = &obex_tl_rfcomm_cb.ccb[handle-1]; + } + } + return p_ccb; +} + +static tOBEX_TL_RFCOMM_CCB *find_ccb_by_rfc_handle(UINT16 rfc_handle) +{ + tOBEX_TL_RFCOMM_CCB *p_ccb = NULL; + for(int i = 0; i < OBEX_TL_RFCOMM_NUM_CONN; ++i) { + if (obex_tl_rfcomm_cb.ccb[i].allocated && obex_tl_rfcomm_cb.ccb[i].rfc_handle == rfc_handle) { + p_ccb = &obex_tl_rfcomm_cb.ccb[i]; + break; + } + } + return p_ccb; +} + +static tOBEX_TL_RFCOMM_SCB *find_scb_by_handle(UINT16 handle) +{ + tOBEX_TL_RFCOMM_SCB *p_scb = NULL; + handle = handle >> 8; + if (handle > 0 && handle <= OBEX_TL_RFCOMM_NUM_SERVER) { + if (obex_tl_rfcomm_cb.scb[handle-1].allocated == handle) { + p_scb = &obex_tl_rfcomm_cb.scb[handle-1]; + } + } + return p_scb; +} + +static tOBEX_TL_RFCOMM_SCB *find_scb_by_rfc_handle(UINT16 rfc_handle) +{ + tOBEX_TL_RFCOMM_SCB *p_scb = NULL; + for(int i = 0; i < OBEX_TL_RFCOMM_NUM_SERVER; ++i) { + if (obex_tl_rfcomm_cb.scb[i].allocated && obex_tl_rfcomm_cb.scb[i].rfc_handle == rfc_handle) { + p_scb = &obex_tl_rfcomm_cb.scb[i]; + break; + } + } + return p_scb; +} + +static tOBEX_TL_RFCOMM_SCB *find_scb_by_scn(UINT16 scn) +{ + tOBEX_TL_RFCOMM_SCB *p_scb = NULL; + for(int i = 0; i < OBEX_TL_RFCOMM_NUM_SERVER; ++i) { + if (obex_tl_rfcomm_cb.scb[i].allocated && obex_tl_rfcomm_cb.scb[i].scn == scn) { + p_scb = &obex_tl_rfcomm_cb.scb[i]; + break; + } + } + return p_scb; +} + +static void rfcomm_mgmt_event_handler(tOBEX_TL_RFCOMM_CCB *p_ccb, UINT32 code) +{ + tOBEX_TL_MSG msg = {0}; + msg.any.hdl = p_ccb->allocated; + switch (code) + { + case PORT_SUCCESS: + /* event already handled, do nothing */ + break; + default: + /* other event, disconnect */ + obex_tl_rfcomm_cb.callback(OBEX_TL_DIS_CONN_EVT, &msg); + free_ccb(p_ccb); + break; + } +} + +static void rfcomm_client_mgmt_callback(UINT32 code, UINT16 rfc_handle, void* data) +{ + tOBEX_TL_RFCOMM_CCB *p_ccb = find_ccb_by_rfc_handle(rfc_handle); + if (p_ccb == NULL) { + OBEX_TL_RFCOMM_TRACE_DEBUG("No ccb to handle rfcomm event\n"); + return; + } + /* connection opened, handle event here */ + if (code == PORT_SUCCESS) { + assert(data != NULL); + tPORT_MGMT_CL_CALLBACK_ARG *cl_mgmt_cb_arg = (tPORT_MGMT_CL_CALLBACK_ARG *)data; + p_ccb->mtu = cl_mgmt_cb_arg->peer_mtu; + + tOBEX_TL_MSG msg = {0}; + msg.conn_open.hdl = p_ccb->allocated; + msg.conn_open.peer_mtu = p_ccb->mtu; + msg.conn_open.our_mtu = p_ccb->mtu; + obex_tl_rfcomm_cb.callback(OBEX_TL_CONN_OPEN_EVT, &msg); + } + rfcomm_mgmt_event_handler(p_ccb, code); +} + +static void rfcomm_server_mgmt_callback(UINT32 code, UINT16 rfc_handle, void* data) +{ + tOBEX_TL_RFCOMM_CCB *p_ccb = NULL; + /* incoming connection, handle event here */ + if (code == PORT_SUCCESS) { + assert(data != NULL); + tOBEX_TL_RFCOMM_SCB *p_scb = find_scb_by_rfc_handle(rfc_handle); + tPORT_MGMT_SR_CALLBACK_ARG *sr_mgmt_cb_arg = (tPORT_MGMT_SR_CALLBACK_ARG *)data; + if (p_scb == NULL) { + OBEX_TL_RFCOMM_TRACE_WARNING("No scb to this rfcomm connection\n"); + /* tell rfcomm to reject this connection */ + sr_mgmt_cb_arg->accept = FALSE; + return; + } + + /* try to find p_ccb with this rfc_handle, we expect to get a NULL */ + p_ccb = find_ccb_by_rfc_handle(rfc_handle); + if (p_ccb == NULL) { + p_ccb = allocate_ccb(); + if (p_ccb == NULL) { + OBEX_TL_RFCOMM_TRACE_WARNING("can not allocate a ccb for new connection\n"); + sr_mgmt_cb_arg->accept = FALSE; + return; + } + } + else { + OBEX_TL_RFCOMM_TRACE_WARNING("found duplicate rfcomm connection\n"); + } + + p_ccb->initiator = FALSE; + p_ccb->rfc_handle = rfc_handle; + p_ccb->scn = p_scb->scn; + p_ccb->mtu = sr_mgmt_cb_arg->peer_mtu; + /* get peer bd_addr */ + PORT_CheckConnection(rfc_handle, FALSE, p_ccb->addr, NULL); + + tOBEX_TL_MSG msg = {0}; + msg.conn_income.hdl = p_ccb->allocated; + msg.conn_income.peer_mtu = p_ccb->mtu; + msg.conn_income.our_mtu = p_ccb->mtu; + msg.conn_income.svr_hdl = (p_scb->allocated << 8); + obex_tl_rfcomm_cb.callback(OBEX_TL_CONN_INCOME_EVT, &msg); + } + else { + /* other event, it means server is connected */ + p_ccb = find_ccb_by_rfc_handle(rfc_handle); + if (p_ccb == NULL) { + OBEX_TL_RFCOMM_TRACE_DEBUG("No ccb to handle rfcomm event\n"); + return; + } + } + rfcomm_mgmt_event_handler(p_ccb, code); +} + +static int rfcomm_data_callback(UINT16 rfc_handle, UINT8 *p_buf, UINT16 len, int type) +{ + tOBEX_TL_RFCOMM_CCB *p_ccb = find_ccb_by_rfc_handle(rfc_handle); + if (p_ccb != NULL && type == DATA_CO_CALLBACK_TYPE_INCOMING) { + tOBEX_TL_MSG msg = {0}; + msg.data.hdl = p_ccb->allocated; + msg.data.p_buf = (BT_HDR *)p_buf; + obex_tl_rfcomm_cb.callback(OBEX_TL_DATA_EVT, &msg); + PORT_FlowControl_GiveCredit(rfc_handle, TRUE, 1); + } + else if(p_buf != NULL) { + osi_free(p_buf); + } + return 1; +} + +static void rfcomm_event_callback(UINT32 code, UINT16 rfc_handle) +{ + tOBEX_TL_RFCOMM_CCB *p_ccb = find_ccb_by_rfc_handle(rfc_handle); + if (p_ccb == NULL) { + OBEX_TL_RFCOMM_TRACE_WARNING("No ccb to handle rfcomm event\n"); + return; + } + + if (code & PORT_EV_FC) { + tOBEX_TL_MSG msg = {0}; + msg.any.hdl = p_ccb->allocated; + if (code & PORT_EV_FCS) { + obex_tl_rfcomm_cb.callback(OBEX_TL_UNCONGEST_EVT, &msg); + } + else { + obex_tl_rfcomm_cb.callback(OBEX_TL_CONGEST_EVT, &msg); + } + } +} + +void obex_tl_rfcomm_init(tOBEX_TL_CBACK *callback) +{ + assert(callback != NULL); +#if (OBEX_DYNAMIC_MEMORY) + if (!obex_tl_rfcomm_cb_ptr) { + obex_tl_rfcomm_cb_ptr = (tOBEX_TL_RFCOMM_CB *)osi_malloc(sizeof(tOBEX_TL_RFCOMM_CB)); + if (!obex_tl_rfcomm_cb_ptr) { + OBEX_TL_RFCOMM_TRACE_ERROR("OBEX over RFCOMM transport layer initialize failed, no memory\n"); + assert(0); + } + } +#endif /* #if (OBEX_DYNAMIC_MEMORY) */ + memset(&obex_tl_rfcomm_cb, 0, sizeof(tOBEX_TL_RFCOMM_CB)); + obex_tl_rfcomm_cb.callback = callback; + obex_tl_rfcomm_cb.trace_level = BT_TRACE_LEVEL_ERROR; +} + +void obex_tl_rfcomm_deinit(void) +{ +#if (OBEX_DYNAMIC_MEMORY) + if (obex_tl_rfcomm_cb_ptr) { + osi_free(obex_tl_rfcomm_cb_ptr); + obex_tl_rfcomm_cb_ptr = NULL; + } +#endif /* #if (OBEX_DYNAMIC_MEMORY) */ +} + +UINT16 obex_tl_rfcomm_connect(tOBEX_TL_SVR_INFO *server) +{ + tOBEX_TL_RFCOMM_CCB *p_ccb = allocate_ccb(); + if (p_ccb == NULL) { + return 0; + } + + BTM_SetSecurityLevel(TRUE, "", BTM_SEC_SERVICE_OBEX, server->rfcomm.sec_mask, BT_PSM_RFCOMM, BTM_SEC_PROTO_RFCOMM, server->rfcomm.scn); + if (RFCOMM_CreateConnection(UUID_PROTOCOL_OBEX, server->rfcomm.scn, FALSE, server->rfcomm.pref_mtu, + server->rfcomm.addr, &p_ccb->rfc_handle, rfcomm_client_mgmt_callback) != PORT_SUCCESS) { + free_ccb(p_ccb); + return 0; + } + + /* set up data callback, event mask and event callback */ + PORT_SetDataCOCallback(p_ccb->rfc_handle, rfcomm_data_callback); + PORT_SetEventMask(p_ccb->rfc_handle, OBEX_TL_RFCOMM_EVENT_MARK); + PORT_SetEventCallback(p_ccb->rfc_handle, rfcomm_event_callback); + + bdcpy(p_ccb->addr, server->rfcomm.addr); + p_ccb->scn = server->rfcomm.scn; + p_ccb->initiator = TRUE; + + return p_ccb->allocated; +} + +void obex_tl_rfcomm_disconnect(UINT16 handle) +{ + tOBEX_TL_RFCOMM_CCB *p_ccb = find_ccb_by_handle(handle); + if (p_ccb != NULL) { + RFCOMM_RemoveConnection(p_ccb->rfc_handle); + free_ccb(p_ccb); + } +} + +UINT16 obex_tl_rfcomm_send(UINT16 handle, BT_HDR *p_buf) +{ + UINT16 ret = OBEX_TL_FAILED; + tOBEX_TL_RFCOMM_CCB *p_ccb = find_ccb_by_handle(handle); + do { + if (p_ccb == NULL) { + osi_free(p_buf); + break; + } + + /* Can not send data size larger than MTU */ + /* Offset should not smaller than OBEX_TL_RFCOMM_BT_HDR_MIN_OFFSET */ + if (p_buf->len > p_ccb->mtu || p_buf->offset < OBEX_TL_RFCOMM_BT_HDR_MIN_OFFSET) { + osi_free(p_buf); + break; + } + + if (PORT_Write(p_ccb->rfc_handle, p_buf) == PORT_SUCCESS) { + ret = OBEX_TL_SUCCESS; + } + } while (0); + return ret; +} + +UINT16 obex_tl_rfcomm_bind(tOBEX_TL_SVR_INFO *server) +{ + tOBEX_TL_RFCOMM_SCB *p_scb = find_scb_by_scn(server->rfcomm.scn); + if (p_scb != NULL) { + /* scn already used */ + return 0; + } + + p_scb = allocate_scb(); + if (p_scb == NULL) { + OBEX_TL_RFCOMM_TRACE_WARNING("Can not allocate scb, out of number\n"); + return 0; + } + + BTM_SetSecurityLevel(FALSE, "", BTM_SEC_SERVICE_OBEX, server->rfcomm.sec_mask, BT_PSM_RFCOMM, BTM_SEC_PROTO_RFCOMM, server->rfcomm.scn); + if (RFCOMM_CreateConnection(UUID_PROTOCOL_OBEX, server->rfcomm.scn, TRUE, server->rfcomm.pref_mtu, + server->rfcomm.addr, &p_scb->rfc_handle, rfcomm_server_mgmt_callback) != PORT_SUCCESS) { + free_scb(p_scb); + return 0; + } + + /* set up data callback, event mask and event callback */ + PORT_SetDataCOCallback(p_scb->rfc_handle, rfcomm_data_callback); + PORT_SetEventMask(p_scb->rfc_handle, OBEX_TL_RFCOMM_EVENT_MARK); + PORT_SetEventCallback(p_scb->rfc_handle, rfcomm_event_callback); + + p_scb->scn = server->rfcomm.scn; + + /* left shift 8 bits as server handle, avoid confuse with connection handle */ + return (p_scb->allocated << 8); +} + +void obex_tl_rfcomm_unbind(UINT16 handle) +{ + tOBEX_TL_RFCOMM_SCB *p_scb = find_scb_by_handle(handle); + if (p_scb) { + tOBEX_TL_RFCOMM_CCB *p_ccb = NULL; + while ((p_ccb = find_ccb_by_rfc_handle(p_scb->rfc_handle)) != NULL) { + RFCOMM_RemoveConnection(p_ccb->rfc_handle); + tOBEX_TL_MSG msg = {0}; + msg.any.hdl = p_ccb->allocated; + obex_tl_rfcomm_cb.callback(OBEX_TL_DIS_CONN_EVT, &msg); + free_ccb(p_ccb); + } + RFCOMM_RemoveServer(p_scb->rfc_handle); + free_scb(p_scb); + } +} + +static tOBEX_TL_OPS obex_tl_rfcomm_ops = { + .init = obex_tl_rfcomm_init, + .deinit = obex_tl_rfcomm_deinit, + .connect = obex_tl_rfcomm_connect, + .disconnect = obex_tl_rfcomm_disconnect, + .bind = obex_tl_rfcomm_bind, + .unbind = obex_tl_rfcomm_unbind, + .send = obex_tl_rfcomm_send +}; + +/******************************************************************************* +** +** Function obex_tl_rfcomm_ops_get +** +** Description Get the operation function structure pointer of OBEX over +** RFCOMM transport layer +** +** Returns Pointer to operation function structure +** +*******************************************************************************/ +tOBEX_TL_OPS *obex_tl_rfcomm_ops_get(void) +{ + return &obex_tl_rfcomm_ops; +} + +#endif /* #if (OBEX_INCLUDED == TRUE && RFCOMM_INCLUDED == TRUE) */ diff --git a/components/bt/host/nimble/nimble b/components/bt/host/nimble/nimble index 275e6820cda2..46f1139a0552 160000 --- a/components/bt/host/nimble/nimble +++ b/components/bt/host/nimble/nimble @@ -1 +1 @@ -Subproject commit 275e6820cda2862e636c35b1acecfe67978684c7 +Subproject commit 46f1139a055246d05c0744c2195c5f82155b5e6d diff --git a/components/esp_driver_cam/isp_dvp/src/esp_cam_ctlr_isp_dvp.c b/components/esp_driver_cam/isp_dvp/src/esp_cam_ctlr_isp_dvp.c index 10446d3749c2..dc41abbb2c41 100644 --- a/components/esp_driver_cam/isp_dvp/src/esp_cam_ctlr_isp_dvp.c +++ b/components/esp_driver_cam/isp_dvp/src/esp_cam_ctlr_isp_dvp.c @@ -495,28 +495,28 @@ static esp_err_t s_isp_io_init(isp_dvp_controller_t *dvp_ctlr, const esp_cam_ctl .pull_up_en = true, }; - if (ctlr_config->pclk_io) { + if (ctlr_config->pclk_io >= 0) { gpio_conf.pin_bit_mask = 1ULL << ctlr_config->pclk_io; ESP_RETURN_ON_ERROR(gpio_config(&gpio_conf), TAG, "failed to configure pclk gpio"); ESP_LOGD(TAG, "pclk_io: %d, dvp_pclk_sig: %"PRId32, ctlr_config->pclk_io, isp_hw_info.dvp_ctlr[dvp_ctlr->id].dvp_pclk_sig); esp_rom_gpio_connect_in_signal(ctlr_config->pclk_io, isp_hw_info.dvp_ctlr[dvp_ctlr->id].dvp_pclk_sig, false); } - if (ctlr_config->hsync_io) { + if (ctlr_config->hsync_io >= 0) { gpio_conf.pin_bit_mask = 1ULL << ctlr_config->hsync_io; ESP_RETURN_ON_ERROR(gpio_config(&gpio_conf), TAG, "failed to configure hsync gpio"); ESP_LOGD(TAG, "hsync_io: %d, dvp_hsync_sig: %"PRId32, ctlr_config->hsync_io, isp_hw_info.dvp_ctlr[dvp_ctlr->id].dvp_hsync_sig); esp_rom_gpio_connect_in_signal(ctlr_config->hsync_io, isp_hw_info.dvp_ctlr[dvp_ctlr->id].dvp_hsync_sig, false); } - if (ctlr_config->vsync_io) { + if (ctlr_config->vsync_io >= 0) { gpio_conf.pin_bit_mask = 1ULL << ctlr_config->vsync_io; ESP_RETURN_ON_ERROR(gpio_config(&gpio_conf), TAG, "failed to configure vsync gpio"); ESP_LOGD(TAG, "vsync_io: %d, dvp_vsync_sig: %"PRId32, ctlr_config->vsync_io, isp_hw_info.dvp_ctlr[dvp_ctlr->id].dvp_vsync_sig); esp_rom_gpio_connect_in_signal(ctlr_config->vsync_io, isp_hw_info.dvp_ctlr[dvp_ctlr->id].dvp_vsync_sig, false); } - if (ctlr_config->de_io) { + if (ctlr_config->de_io >= 0) { gpio_conf.pin_bit_mask = 1ULL << ctlr_config->de_io; ESP_RETURN_ON_ERROR(gpio_config(&gpio_conf), TAG, "failed to configure de gpio"); ESP_LOGD(TAG, "de_io: %d, dvp_de_sig: %"PRId32, ctlr_config->de_io, isp_hw_info.dvp_ctlr[dvp_ctlr->id].dvp_de_sig); diff --git a/components/esp_driver_i2s/i2s_tdm.c b/components/esp_driver_i2s/i2s_tdm.c index f0dce4d55a1c..01236591f486 100644 --- a/components/esp_driver_i2s/i2s_tdm.c +++ b/components/esp_driver_i2s/i2s_tdm.c @@ -104,8 +104,8 @@ static esp_err_t i2s_tdm_set_slot(i2s_chan_handle_t handle, const i2s_tdm_slot_c handle->active_slot = slot_cfg->slot_mode == I2S_SLOT_MODE_MONO ? 1 : __builtin_popcount(slot_cfg->slot_mask); uint32_t max_slot_num = 32 - __builtin_clz(slot_cfg->slot_mask); handle->total_slot = slot_cfg->total_slot < max_slot_num ? max_slot_num : slot_cfg->total_slot; - handle->total_slot = handle->total_slot < 2 ? 2 : handle->total_slot; // At least two slots in a frame - + // At least two slots in a frame if not using PCM short format + handle->total_slot = ((handle->total_slot < 2) && (slot_cfg->ws_width != 1)) ? 2 : handle->total_slot; uint32_t buf_size = i2s_get_buf_size(handle, slot_cfg->data_bit_width, handle->dma.frame_num); /* The DMA buffer need to re-allocate if the buffer size changed */ if (handle->dma.buf_size != buf_size) { diff --git a/components/esp_driver_isp/include/esp_private/isp_private.h b/components/esp_driver_isp/include/esp_private/isp_private.h index a0e6913ca59e..34d3a0bdb7c6 100644 --- a/components/esp_driver_isp/include/esp_private/isp_private.h +++ b/components/esp_driver_isp/include/esp_private/isp_private.h @@ -111,6 +111,7 @@ bool esp_isp_ae_isr(isp_proc_handle_t proc, uint32_t ae_events); bool esp_isp_awb_isr(isp_proc_handle_t proc, uint32_t awb_events); bool esp_isp_sharpen_isr(isp_proc_handle_t proc, uint32_t sharp_events); bool esp_isp_hist_isr(isp_proc_handle_t proc, uint32_t hist_events); +esp_err_t esp_isp_enable_yuv_submodules(isp_proc_handle_t proc, bool en); #ifdef __cplusplus } diff --git a/components/esp_driver_isp/src/isp_af.c b/components/esp_driver_isp/src/isp_af.c index 28c2055328d0..2257103bb5ec 100644 --- a/components/esp_driver_isp/src/isp_af.c +++ b/components/esp_driver_isp/src/isp_af.c @@ -81,6 +81,7 @@ esp_err_t esp_isp_new_af_controller(isp_proc_handle_t isp_proc, const esp_isp_af { esp_err_t ret = ESP_FAIL; ESP_RETURN_ON_FALSE(isp_proc && af_config && ret_hdl, ESP_ERR_INVALID_ARG, TAG, "invalid argument: null pointer"); + ESP_RETURN_ON_ERROR(esp_isp_enable_yuv_submodules(isp_proc, true), TAG, "failed to enable YUV submodules"); bool rgb2yuv_en = isp_ll_is_rgb2yuv_enabled(isp_proc->hal.hw); bool demosaic_en = isp_ll_is_demosaic_enabled(isp_proc->hal.hw); @@ -154,6 +155,7 @@ esp_err_t esp_isp_del_af_controller(isp_af_ctlr_t af_ctlr) // Deregister the AF ISR ESP_RETURN_ON_FALSE(esp_isp_deregister_isr(af_ctlr->isp_proc, ISP_SUBMODULE_AF) == ESP_OK, ESP_FAIL, TAG, "fail to deregister ISR"); + ESP_RETURN_ON_ERROR(esp_isp_enable_yuv_submodules(af_ctlr->isp_proc, false), TAG, "failed to disable YUV submodules"); s_isp_declaim_af_controller(af_ctlr); s_isp_af_free_controller(af_ctlr); diff --git a/components/esp_driver_isp/src/isp_color.c b/components/esp_driver_isp/src/isp_color.c index 09e118c62685..f2430d1eb838 100644 --- a/components/esp_driver_isp/src/isp_color.c +++ b/components/esp_driver_isp/src/isp_color.c @@ -49,6 +49,7 @@ esp_err_t esp_isp_color_enable(isp_proc_handle_t proc) { ESP_RETURN_ON_FALSE(proc, ESP_ERR_INVALID_ARG, TAG, "invalid argument: null pointer"); ESP_RETURN_ON_FALSE(proc->color_fsm == ISP_FSM_INIT, ESP_ERR_INVALID_STATE, TAG, "color is enabled already"); + ESP_RETURN_ON_ERROR(esp_isp_enable_yuv_submodules(proc, true), TAG, "failed to enable YUV submodules"); isp_ll_color_clk_enable(proc->hal.hw, true); isp_ll_color_enable(proc->hal.hw, true); @@ -61,6 +62,7 @@ esp_err_t esp_isp_color_disable(isp_proc_handle_t proc) { ESP_RETURN_ON_FALSE(proc, ESP_ERR_INVALID_ARG, TAG, "invalid argument: null pointer"); ESP_RETURN_ON_FALSE(proc->color_fsm == ISP_FSM_ENABLE, ESP_ERR_INVALID_STATE, TAG, "color isn't enabled yet"); + ESP_RETURN_ON_ERROR(esp_isp_enable_yuv_submodules(proc, false), TAG, "failed to disable YUV submodules"); isp_ll_color_enable(proc->hal.hw, false); isp_ll_color_clk_enable(proc->hal.hw, false); diff --git a/components/esp_driver_isp/src/isp_core.c b/components/esp_driver_isp/src/isp_core.c index f964aaa1ccba..b39865ce2995 100644 --- a/components/esp_driver_isp/src/isp_core.c +++ b/components/esp_driver_isp/src/isp_core.c @@ -390,3 +390,29 @@ esp_err_t esp_isp_deregister_isr(isp_proc_handle_t proc, isp_submodule_t submodu return ESP_OK; } + +esp_err_t esp_isp_enable_yuv_submodules(isp_proc_handle_t proc, bool en) +{ + ESP_RETURN_ON_FALSE(proc, ESP_ERR_INVALID_ARG, TAG, "invalid argument: null pointer"); + + bool rgb2yuv = false; + bool yuv2rgb = false; + + if (proc->out_color_format.color_space == COLOR_SPACE_RGB) { + rgb2yuv = true; + yuv2rgb = true; + } else if (proc->out_color_format.color_space == COLOR_SPACE_YUV) { + rgb2yuv = true; + } + + portENTER_CRITICAL(&proc->spinlock); + if (rgb2yuv) { + isp_ll_enable_rgb2yuv(proc->hal.hw, en); + } + if (yuv2rgb) { + isp_ll_enable_yuv2rgb(proc->hal.hw, en); + } + portEXIT_CRITICAL(&proc->spinlock); + + return ESP_OK; +} diff --git a/components/esp_driver_isp/src/isp_sharpen.c b/components/esp_driver_isp/src/isp_sharpen.c index abd88956ce28..84722aa88414 100644 --- a/components/esp_driver_isp/src/isp_sharpen.c +++ b/components/esp_driver_isp/src/isp_sharpen.c @@ -51,6 +51,7 @@ esp_err_t esp_isp_sharpen_enable(isp_proc_handle_t proc) { ESP_RETURN_ON_FALSE(proc, ESP_ERR_INVALID_ARG, TAG, "invalid argument: null pointer"); ESP_RETURN_ON_FALSE(proc->sharpen_fsm == ISP_FSM_INIT, ESP_ERR_INVALID_STATE, TAG, "sharpen is enabled already"); + ESP_RETURN_ON_ERROR(esp_isp_enable_yuv_submodules(proc, true), TAG, "failed to enable YUV submodules"); isp_ll_sharp_clk_enable(proc->hal.hw, true); isp_ll_enable_intr(proc->hal.hw, ISP_LL_EVENT_SHARP_FRAME, true); @@ -64,6 +65,7 @@ esp_err_t esp_isp_sharpen_disable(isp_proc_handle_t proc) { ESP_RETURN_ON_FALSE(proc, ESP_ERR_INVALID_ARG, TAG, "invalid argument: null pointer"); ESP_RETURN_ON_FALSE(proc->sharpen_fsm == ISP_FSM_ENABLE, ESP_ERR_INVALID_STATE, TAG, "sharpen isn't enabled yet"); + ESP_RETURN_ON_ERROR(esp_isp_enable_yuv_submodules(proc, false), TAG, "failed to disable YUV submodules"); isp_ll_sharp_enable(proc->hal.hw, false); isp_ll_enable_intr(proc->hal.hw, ISP_LL_EVENT_SHARP_FRAME, false); diff --git a/components/esp_https_ota/src/esp_https_ota.c b/components/esp_https_ota/src/esp_https_ota.c index d86ab41d2857..ba8a5e5fc63b 100644 --- a/components/esp_https_ota/src/esp_https_ota.c +++ b/components/esp_https_ota/src/esp_https_ota.c @@ -562,7 +562,7 @@ esp_err_t esp_https_ota_perform(esp_https_ota_handle_t https_ota_handle) esp_err_t err; int data_read; - const int erase_size = handle->bulk_flash_erase ? (handle->image_length > 0 ? handle->image_length : OTA_SIZE_UNKNOWN) : OTA_WITH_SEQUENTIAL_WRITES; + const size_t erase_size = handle->bulk_flash_erase ? (handle->image_length > 0 ? handle->image_length : OTA_SIZE_UNKNOWN) : OTA_WITH_SEQUENTIAL_WRITES; switch (handle->state) { case ESP_HTTPS_OTA_BEGIN: err = esp_ota_begin(handle->partition.staging, erase_size, &handle->update_handle); diff --git a/components/hal/esp32p4/include/hal/isp_ll.h b/components/hal/esp32p4/include/hal/isp_ll.h index 00712b12564c..9eb827208d45 100644 --- a/components/hal/esp32p4/include/hal/isp_ll.h +++ b/components/hal/esp32p4/include/hal/isp_ll.h @@ -396,15 +396,15 @@ static inline bool isp_ll_set_output_data_color_format(isp_dev_t *hw, color_spac case COLOR_PIXEL_RGB888: hw->cntl.isp_out_type = 2; hw->cntl.demosaic_en = 1; - hw->cntl.rgb2yuv_en = 1; - hw->cntl.yuv2rgb_en = 1; + hw->cntl.rgb2yuv_en = 0; + hw->cntl.yuv2rgb_en = 0; valid = true; break; case COLOR_PIXEL_RGB565: hw->cntl.isp_out_type = 4; hw->cntl.demosaic_en = 1; - hw->cntl.rgb2yuv_en = 1; - hw->cntl.yuv2rgb_en = 1; + hw->cntl.rgb2yuv_en = 0; + hw->cntl.yuv2rgb_en = 0; valid = true; break; default: @@ -456,6 +456,28 @@ static inline void isp_ll_enable_line_end_packet_exist(isp_dev_t *hw, bool en) hw->frame_cfg.hsync_end_exist = en; } +/** + * @brief Enable rgb2yuv + * + * @param[in] hw Hardware instance address + * @param[in] en Enable / Disable + */ +static inline void isp_ll_enable_rgb2yuv(isp_dev_t *hw, bool en) +{ + hw->cntl.rgb2yuv_en = en; +} + +/** + * @brief Enable yuv2rgb + * + * @param[in] hw Hardware instance address + * @param[in] en Enable / Disable + */ +static inline void isp_ll_enable_yuv2rgb(isp_dev_t *hw, bool en) +{ + hw->cntl.yuv2rgb_en = en; +} + /** * @brief Get if demosaic is enabled * diff --git a/components/hal/i2s_hal.c b/components/hal/i2s_hal.c index 523ff331b79b..c445af2761b3 100644 --- a/components/hal/i2s_hal.c +++ b/components/hal/i2s_hal.c @@ -331,8 +331,8 @@ void i2s_hal_tdm_set_tx_slot(i2s_hal_context_t *hal, bool is_slave, const i2s_ha uint32_t msk = slot_cfg->tdm.slot_mask; /* Get the maximum slot number */ cnt = 32 - __builtin_clz(msk); - /* There should be at least 2 slots in total even for mono mode */ - cnt = cnt < 2 ? 2 : cnt; + /* Except PCM short format (ws_width = 1), there should be at least 2 slots in total even for mono mode */ + cnt = ((cnt < 2) && (slot_cfg->tdm.ws_width != 1)) ? 2 : cnt; uint32_t total_slot = slot_cfg->tdm.total_slot > cnt ? slot_cfg->tdm.total_slot : cnt; i2s_ll_tx_reset(hal->dev); i2s_ll_tx_set_slave_mod(hal->dev, is_slave); //TX Slave @@ -365,8 +365,8 @@ void i2s_hal_tdm_set_rx_slot(i2s_hal_context_t *hal, bool is_slave, const i2s_ha uint32_t msk = slot_cfg->tdm.slot_mask; /* Get the maximum slot number */ cnt = 32 - __builtin_clz(msk); - /* There should be at least 2 slots in total even for mono mode */ - cnt = cnt < 2 ? 2 : cnt; + /* Except PCM short format (ws_width = 1), there should be at least 2 slots in total even for mono mode */ + cnt = ((cnt < 2) && (slot_cfg->tdm.ws_width != 1)) ? 2 : cnt; uint32_t total_slot = slot_cfg->tdm.total_slot > cnt ? slot_cfg->tdm.total_slot : cnt; i2s_ll_rx_reset(hal->dev); i2s_ll_rx_set_slave_mod(hal->dev, is_slave); //RX Slave diff --git a/docs/en/api-guides/lwip.rst b/docs/en/api-guides/lwip.rst index 7e946181c9f4..f61448bdb25c 100644 --- a/docs/en/api-guides/lwip.rst +++ b/docs/en/api-guides/lwip.rst @@ -474,7 +474,7 @@ The number of IP addresses returned by network database APIs such as ``getaddrin In the implementation of ``getaddrinfo()``, the canonical name is not available. Therefore, the ``ai_canonname`` field of the first returned ``addrinfo`` structure will always refer to the ``nodename`` argument or a string with the same contents. -The ``getaddrinfo()`` system call in lwIP within ESP-IDF has a limitation when using ``AF_UNSPEC``, as it defaults to returning only an IPv4 address in dual stack mode. This can cause issues in IPv6-only networks. To handle this, a workaround involves making two sequential calls to ``getaddrinfo()``: the first with AF_INET to query for IPv4 addresses, and the second with AF_INET6 to retrieve IPv6 addresses. To address this, the custom ``esp_getaddrinfo()`` function has been added to the lwIP port layer to handle both IPv4 and IPv6 addresses when AF_UNSPEC is used. The :ref:`CONFIG_LWIP_USE_ESP_GETADDRINFO` option, available when both IPv4 and IPv6 are enabled, controls whether ``esp_getaddrinfo()`` or the default lwIP implementation is used. It is disabled by default. +The ``getaddrinfo()`` system call in lwIP within ESP-IDF has a limitation when using ``AF_UNSPEC``, as it defaults to returning only an IPv4 address in dual stack mode. This can cause issues in IPv6-only networks. To handle this, a workaround involves making two sequential calls to ``getaddrinfo()``: the first with ``AF_INET`` to query for IPv4 addresses, and the second with ``AF_INET6`` to retrieve IPv6 addresses. To provide a more robust solution, the custom ``esp_getaddrinfo()`` function has been added to the lwIP port layer to handle both IPv4 and IPv6 addresses when ``AF_UNSPEC`` is used. The :ref:`CONFIG_LWIP_USE_ESP_GETADDRINFO` option, available when both IPv4 and IPv6 are enabled, controls whether ``esp_getaddrinfo()`` or ``getaddrinfo()`` is used. It is disabled by default. Calling ``send()`` or ``sendto()`` repeatedly on a UDP socket may eventually fail with ``errno`` equal to ``ENOMEM``. This failure occurs due to the limitations of buffer sizes in the lower-layer network interface drivers. If all driver transmit buffers are full, the UDP transmission will fail. For applications that transmit a high volume of UDP datagrams and aim to avoid any dropped datagrams by the sender, it is advisable to implement error code checking and employ a retransmission mechanism with a short delay. diff --git a/docs/en/api-guides/openthread.rst b/docs/en/api-guides/openthread.rst index fedad729badc..a0d8f38eee27 100644 --- a/docs/en/api-guides/openthread.rst +++ b/docs/en/api-guides/openthread.rst @@ -115,8 +115,7 @@ In the OpenThread protocol stack, defining macros to enable features and configu .. note:: - The priority of the above configuration methods, from highest to lowest, is as follows: - Configuration Menu → User-defined Header File → openthread-core-esp32x-xxx-config.h → OpenThread Stack Default Configuration + The priority of the above configuration methods, from highest to lowest, is as follows: Configuration Menu → User-defined Header File → openthread-core-esp32x-xxx-config.h → OpenThread Stack Default Configuration The OpenThread Border Router ---------------------------- diff --git a/docs/en/api-guides/partition-tables.rst b/docs/en/api-guides/partition-tables.rst index b279cdaa65b3..51313e3d71e4 100644 --- a/docs/en/api-guides/partition-tables.rst +++ b/docs/en/api-guides/partition-tables.rst @@ -54,7 +54,7 @@ Here is the summary printed for the "Factory app, two OTA definitions" configura Creating Custom Tables ---------------------- -If you choose "Custom partition table CSV" in menuconfig then you can also enter the name of a CSV file (in the project directory) to use for your partition table. The CSV file can describe any number of definitions for the table you need. +If you choose "Custom partition table CSV" in ``menuconfig``, then you can also enter the name of a CSV file (in the project directory) to use for your partition table. The CSV file can describe any number of definitions for the table you need. The CSV format is the same format as printed in the summaries shown above. However, not all fields are required in the CSV. For example, here is the "input" CSV for the OTA partition table: @@ -135,18 +135,18 @@ See enum :cpp:type:`esp_partition_subtype_t` for the full list of subtypes defin * When type is ``bootloader``, the SubType field can be specified as: - - ``primary`` (0x00). This is the 2nd stage bootloader, located at the {IDF_TARGET_CONFIG_BOOTLOADER_OFFSET_IN_FLASH} address in flash memory. The tool automatically determines the appropriate size and offset for this subtype, so any size or offset specified for this subtype will be ignored. You can either leave these fields blank or use ``N/A`` as a placeholder. - - ``ota`` (0x01). This is a temporary bootloader partition used by the bootloader OTA update functionality to download a new image. The tool ignores the size for this subtype, allowing you to leave it blank or use ``N/A``. You can only specify an offset, or leave it blank to have the tool calculate it based on the offsets of previously used partitions. - - ``recovery`` (0x02). This is the recovery bootloader partition used for safely performing OTA updates to the bootloader. The ``gen_esp32part.py`` tool automatically determines the address and size for this partition, so you can leave these fields blank or use ``N/A`` as a placeholder. The address must match an eFuse field, which is defined through a Kconfig option. If the normal bootloader loading path fails, the ROM bootloader will attempt to load the recovery partition at the address specified by the eFuse field. + - ``primary`` (0x00). This is the 2nd stage bootloader, located at the {IDF_TARGET_CONFIG_BOOTLOADER_OFFSET_IN_FLASH} address in flash memory. The tool automatically determines the appropriate size and offset for this subtype, so any size or offset specified for this subtype will be ignored. You can either leave these fields blank or use ``N/A`` as a placeholder. + - ``ota`` (0x01). This is a temporary bootloader partition used by the bootloader OTA update functionality to download a new image. The tool ignores the size for this subtype, allowing you to leave it blank or use ``N/A``. You can only specify an offset, or leave it blank to have the tool calculate it based on the offsets of previously used partitions. + - ``recovery`` (0x02). This is the recovery bootloader partition used for safely performing OTA updates to the bootloader. The ``gen_esp32part.py`` tool automatically determines the address and size for this partition, so you can leave these fields blank or use ``N/A`` as a placeholder. The address must match an eFuse field, which is defined through a Kconfig option. If the normal bootloader loading path fails, the first stage (ROM) bootloader will attempt to load the recovery partition at the address specified by the eFuse field. - The size of the bootloader type is calculated by the ``gen_esp32part.py`` tool based on the specified ``--offset`` (the partition table offset) and ``--primary-partition-offset`` arguments. Specifically, the bootloader size is defined as (:ref:`CONFIG_PARTITION_TABLE_OFFSET` - {IDF_TARGET_CONFIG_BOOTLOADER_OFFSET_IN_FLASH}). This calculated size applies to all subtypes of the bootloader. + The size of the bootloader type is calculated by the ``gen_esp32part.py`` tool based on the specified ``--offset`` (the partition table offset) and ``--primary-partition-offset`` arguments. Specifically, the bootloader size is defined as (:ref:`CONFIG_PARTITION_TABLE_OFFSET` - {IDF_TARGET_CONFIG_BOOTLOADER_OFFSET_IN_FLASH}). This calculated size applies to all subtypes of the bootloader. * When type is ``partition_table``, the SubType field can be specified as: - - ``primary`` (0x00). This is the primary partition table, located at the :ref:`CONFIG_PARTITION_TABLE_OFFSET` address in flash memory. The tool automatically determines the appropriate size and offset for this subtype, so any size or offset specified for this subtype will be ignored. You can either leave these fields blank or use ``N/A`` as a placeholder. - - ``ota`` (0x01). It is a temporary partition table partition used by the partition table OTA update functionality for downloading a new image. The tool ignores the size for this subtype, allowing you to leave it blank or use ``N/A``. You can specify an offset, or leave it blank, in which case the tool will calculate it based on the offsets of previously allocated partitions. + - ``primary`` (0x00). This is the primary partition table, located at the :ref:`CONFIG_PARTITION_TABLE_OFFSET` address in flash memory. The tool automatically determines the appropriate size and offset for this subtype, so any size or offset specified for this subtype will be ignored. You can either leave these fields blank or use ``N/A`` as a placeholder. + - ``ota`` (0x01). It is a temporary partition table partition used by the partition table OTA update functionality for downloading a new image. The tool ignores the size for this subtype, allowing you to leave it blank or use ``N/A``. You can specify an offset, or leave it blank, in which case the tool will calculate it based on the offsets of previously allocated partitions. - The size for the ``partition_table`` type is fixed at ``0x1000`` and applies uniformly across all subtypes of ``partition_table``. + The size for the ``partition_table`` type is fixed at ``0x1000`` and applies uniformly across all subtypes of ``partition_table``. * When type is ``data``, the subtype field can be specified as ``ota`` (0x00), ``phy`` (0x01), ``nvs`` (0x02), nvs_keys (0x04), or a range of other component-specific subtypes (see :cpp:type:`subtype enum `). @@ -220,15 +220,15 @@ Two flags are currently supported, ``encrypted`` and ``readonly``: .. note:: - The following type partitions will always be encrypted, regardless of whether this flag is set or not: + The following type partitions will always be encrypted, regardless of whether this flag is set or not: - .. list:: + .. list:: - - ``app``, - - ``bootloader``, - - ``partition_table``, - - type ``data`` and subtype ``ota``, - - type ``data`` and subtype ``nvs_keys``. + - ``app``, + - ``bootloader``, + - ``partition_table``, + - type ``data`` and subtype ``ota``, + - type ``data`` and subtype ``nvs_keys``. - If ``readonly`` flag is set, the partition will be read-only. This flag is only supported for ``data`` type partitions except ``ota`` and ``coredump`` subtypes. This flag can help to protect against accidental writes to a partition that contains critical device-specific configuration data, e.g., factory data partition. diff --git a/docs/en/api-guides/performance/size.rst b/docs/en/api-guides/performance/size.rst index 0a94ed855e77..9cbaa162439c 100644 --- a/docs/en/api-guides/performance/size.rst +++ b/docs/en/api-guides/performance/size.rst @@ -67,7 +67,7 @@ The map file itself is broken into parts and each part has a heading. The parts .. note:: - Linker map files are generated by the GNU binutils linker ``ld``, not ESP-IDF. You can find additional information online about the linker map file format. This quick summary is written from the perspective of ESP-IDF build system in particular. + Linker map files are generated by the GNU binutils linker ``ld``, not ESP-IDF. You can find additional information online about the linker map file format. This quick summary is written from the perspective of ESP-IDF build system in particular. .. _reducing-overall-size: @@ -94,7 +94,7 @@ The following configuration options reduces the final binary size of almost any .. note:: - In addition to the many configuration items shown here, there are a number of configuration options where changing the option from the default increases binary size. These are not noted here. Where the increase is significant is usually noted in the configuration item help text. + In addition to the many configuration items shown here, there are a number of configuration options where changing the option from the default increases binary size. These are not noted here. Where the increase is significant is usually noted in the configuration item help text. .. _size-targeted-optimizations: @@ -147,9 +147,9 @@ lwIP IPv4 - If IPv4 connectivity is not required, setting :ref:`CONFIG_LWIP_IPV4` to ``false`` will reduce the size of the lwIP, supporting IPv6-only TCP/IP stack. - .. note:: + .. note:: - Before disabling IPv4 support, please note that IPv6 only network environments are not ubiquitous and must be supported in the local network, e.g., by your internet service provider or using constrained local network settings. + Before disabling IPv4 support, please note that IPv6 only network environments are not ubiquitous and must be supported in the local network, e.g., by your internet service provider or using constrained local network settings. .. _picolibc-instead-of-newlib: @@ -224,21 +224,22 @@ The help text for each option has some more information for reference. .. important:: - It is **strongly not recommended to disable all these mbedTLS options**. Only disable options of which you understand the functionality and are certain that it is not needed in the application. In particular: + It is **strongly not recommended to disable all these mbedTLS options**. Only disable options of which you understand the functionality and are certain that it is not needed in the application. In particular: - - Ensure that any TLS server(s) the device connects to can still be used. If the server is controlled by a third party or a cloud service, it is recommended to ensure that the firmware supports at least two of the supported cipher suites in case one is disabled in a future update. - - Ensure that any TLS client(s) that connect to the device can still connect with supported/recommended cipher suites. Note that future versions of client operating systems may remove support for some features, so it is recommended to enable multiple supported cipher suites, or algorithms for redundancy. + - Ensure that any TLS server(s) the device connects to can still be used. If the server is controlled by a third party or a cloud service, it is recommended to ensure that the firmware supports at least two of the supported cipher suites in case one is disabled in a future update. + - Ensure that any TLS client(s) that connect to the device can still connect with supported/recommended cipher suites. Note that future versions of client operating systems may remove support for some features, so it is recommended to enable multiple supported cipher suites, or algorithms for redundancy. - If depending on third party clients or servers, always pay attention to announcements about future changes to supported TLS features. If not, the {IDF_TARGET_NAME} device may become inaccessible if support changes. + If depending on third party clients or servers, always pay attention to announcements about future changes to supported TLS features. If not, the {IDF_TARGET_NAME} device may become inaccessible if support changes. .. only:: CONFIG_ESP_ROM_HAS_MBEDTLS_CRYPTO_LIB - Enabling the config option :ref:`CONFIG_MBEDTLS_USE_CRYPTO_ROM_IMPL` will use the crypto algorithms from mbedTLS library inside the chip ROM. - Disabling the config option :ref:`CONFIG_MBEDTLS_USE_CRYPTO_ROM_IMPL` will use the crypto algorithms from the ESP-IDF mbedtls component library. This will increase the binary size (flash footprint). + Enabling the config option :ref:`CONFIG_MBEDTLS_USE_CRYPTO_ROM_IMPL` will use the crypto algorithms from mbedTLS library inside the chip ROM. + + Disabling the config option :ref:`CONFIG_MBEDTLS_USE_CRYPTO_ROM_IMPL` will use the crypto algorithms from the ESP-IDF mbedtls component library. This will increase the binary size (flash footprint). .. note:: - Not every combination of mbedTLS compile-time config is tested in ESP-IDF. If you find a combination that fails to compile or function as expected, please report the details on `GitHub `_. + Not every combination of mbedTLS compile-time config is tested in ESP-IDF. If you find a combination that fails to compile or function as expected, please report the details on `GitHub `_. VFS @@@ @@ -264,6 +265,7 @@ VFS @@@@ .. list:: + * Enabling :ref:`CONFIG_HEAP_PLACE_FUNCTION_INTO_FLASH` can reduce the IRAM usage and binary size by placing the entirety of the heap functionalities in flash memory. :CONFIG_ESP_ROM_HAS_HEAP_TLSF: * Enabling :ref:`CONFIG_HEAP_TLSF_USE_ROM_IMPL` can reduce the IRAM usage and binary size by linking in the TLSF library of ROM implementation. @@ -278,10 +280,10 @@ VFS 1. Disable the secondary console by setting :ref:`CONFIG_ESP_CONSOLE_SECONDARY` to ``CONFIG_ESP_CONSOLE_SECONDARY_NONE``. 2. Set :ref:`CONFIG_ESP_CONSOLE_UART` to use one of the following: - * ``UART`` reduces the binary size by around 2.5 KB. - * ``USB-Serial-JTAG`` reduces the binary size by around 10 KB and DRAM usage by around 1.5 KB. + * ``UART`` reduces the binary size by around 2.5 KB. + * ``USB-Serial-JTAG`` reduces the binary size by around 10 KB and DRAM usage by around 1.5 KB. - Please note that these size reductions assume the UART/USB-Serial-JTAG driver code is not pulled into the app, if you already use these drivers for other purposes then the savings will be smaller. + Please note that these size reductions assume the UART/USB-Serial-JTAG driver code is not pulled into the app. If these drivers are already used for other purposes, then the savings will be smaller. Bootloader Size diff --git a/docs/en/api-reference/peripherals/camera_driver.rst b/docs/en/api-reference/peripherals/camera_driver.rst index 9023d0a95700..d2338b339bb4 100644 --- a/docs/en/api-reference/peripherals/camera_driver.rst +++ b/docs/en/api-reference/peripherals/camera_driver.rst @@ -212,7 +212,8 @@ This allows the interrupt to run while the cache is disabled, but comes at the c Application Examples -------------------- -* :example:`peripherals/camera/camera_dsi` demonstrates how to use the ``esp_driver_cam`` component to capture signals from a camera sensor and display it on an ILI9881C LCD screen via a DSI interface. +* :example:`peripherals/camera/mipi_isp_dsi` demonstrates how to use the ``esp_driver_cam`` component to capture signals from a MIPI CSI camera sensor via the ISP module and display it on a LCD screen via a DSI interface. +* :example:`peripherals/camera/dvp_isp_dsi` demonstrates how to use the ``esp_driver_cam`` component to capture signals from a DVP camera sensor via the ISP module and display it on a LCD screen via a DSI interface. API Reference ------------- diff --git a/docs/en/api-reference/peripherals/pcnt.rst b/docs/en/api-reference/peripherals/pcnt.rst index 3f7d9a12c089..315de62501a0 100644 --- a/docs/en/api-reference/peripherals/pcnt.rst +++ b/docs/en/api-reference/peripherals/pcnt.rst @@ -104,6 +104,10 @@ If a previously created PCNT channel is no longer needed, it is recommended to r pcnt_channel_handle_t pcnt_chan = NULL; ESP_ERROR_CHECK(pcnt_new_channel(pcnt_unit, &chan_config, &pcnt_chan)); +.. note:: + + In PCNT, the GPIOs involved can be reconfigured for pull-up or pull-down after initializing PCNT using functions such as :cpp:func:`gpio_pullup_en` and :cpp:func:`gpio_pullup_dis`. + .. _pcnt-setup-channel-actions: Set Up Channel Actions diff --git a/docs/zh_CN/api-guides/ble/get-started/ble-data-exchange.rst b/docs/zh_CN/api-guides/ble/get-started/ble-data-exchange.rst index dc0450ccb6f1..6d0b35bc326d 100644 --- a/docs/zh_CN/api-guides/ble/get-started/ble-data-exchange.rst +++ b/docs/zh_CN/api-guides/ble/get-started/ble-data-exchange.rst @@ -654,7 +654,6 @@ LED 特征数据的访问通过 `led_chr_access` 回调函数管理,相关代 简单总结一下,当 GATT 客户端订阅心率测量值时, `gap_event_handler` 将会接收到订阅事件,并将订阅事件传递至 `gatt_svr_subscribe_cb` 回调函数,随后更新心率测量值的订阅状态。在 `heart_rate_task` 线程中,每秒都会检查一次心率测量值的订阅状态,若订阅状态为真,则将心率测量值发送至客户端。 - 总结 ---------------------------- diff --git a/docs/zh_CN/api-guides/lwip.rst b/docs/zh_CN/api-guides/lwip.rst index 9a02f9fbb917..dc88ee7c1310 100644 --- a/docs/zh_CN/api-guides/lwip.rst +++ b/docs/zh_CN/api-guides/lwip.rst @@ -474,6 +474,8 @@ NAPT 和端口转发 在调用 ``getaddrinfo()`` 函数时,不会返回规范名称。因此,第一个返回的 ``addrinfo`` 结构中的 ``ai_canonname`` 字段仅包含 ``nodename`` 参数或相同内容的字符串。 +ESP-IDF 中 lwIP 的 ``getaddrinfo()`` 系统调用在使用 ``AF_UNSPEC`` 时存在限制:双栈模式下默认只返回 IPv4 地址,因此在仅支持 IPv6 的网络中可能会出现问题。为了解决这个问题,可以通过以下方法进行处理:分别调用两次 ``getaddrinfo()``,第一次使用 ``AF_INET`` 查询 IPv4 地址,第二次使用 ``AF_INET6`` 查询 IPv6 地址。为了进一步优化,lwIP 移植层中新增了自定义函数 ``esp_getaddrinfo()``,该函数在使用 ``AF_UNSPEC`` 时能够同时处理 IPv4 和 IPv6 地址。同时启用 IPv4 和 IPv6 后,可通过 :ref:`CONFIG_LWIP_USE_ESP_GETADDRINFO` 选项选择使用自定义的 ``esp_getaddrinfo()`` 或默认的 ``getaddrinfo()`` 实现。``esp_getaddrinfo()`` 默认处于禁用状态。 + 在 UDP 套接字上重复调用 ``send()`` 或 ``sendto()`` 最终可能会导致错误。此时 ``errno`` 报错为 ``ENOMEM``,错误原因是底层网络接口驱动程序中的 buffer 大小有限。当所有驱动程序的传输 buffer 已满时,UDP 传输事务失败。如果应用程序需要发送大量 UDP 数据报,且不希望发送方丢弃数据报,建议检查错误代码,采用短延迟的重传机制。 .. only:: esp32 diff --git a/docs/zh_CN/api-guides/openthread.rst b/docs/zh_CN/api-guides/openthread.rst index 45e09f1ec1f0..548010861dd8 100644 --- a/docs/zh_CN/api-guides/openthread.rst +++ b/docs/zh_CN/api-guides/openthread.rst @@ -113,7 +113,7 @@ OpenThread 宏定义 - 使用 ``openthread-core-esp32x-xxx-config.h`` 的配置:部分宏在 OpenThread private 头文件中已经设置默认值,暂不支持通过 menuconfig 修改,但可以通过用户自定义头文件修改。 - 使用 OpenThread 协议栈默认配置:对于其他宏,OpenThread 协议栈在定义时已设置默认值。 -.. 注意:: +.. note:: 以上四种配置方式,优先级由高到低为:配置菜单 → 用户自定义头文件 → openthread-core-esp32x-xxx-config.h → OpenThread 协议栈默认配置 diff --git a/docs/zh_CN/api-guides/partition-tables.rst b/docs/zh_CN/api-guides/partition-tables.rst index f6f95aec2c5e..6c92cb92d86b 100644 --- a/docs/zh_CN/api-guides/partition-tables.rst +++ b/docs/zh_CN/api-guides/partition-tables.rst @@ -84,6 +84,7 @@ CSV 文件的格式与上面摘要中打印的格式相同,但是在 CSV 文 nvs, data, nvs, , 0x6000, phy_init, data, phy, , 0x1000, factory, app, factory, , 1M, + recoveryBloader, bootloader, recovery, N/A, N/A, ``gen_esp32part.py`` 工具将根据所选的 Kconfig 选项将每个 ``N/A`` 替换为适当的值:引导加载程序的偏移地址为 {IDF_TARGET_CONFIG_BOOTLOADER_OFFSET_IN_FLASH},分区表的偏移地址见 :ref:`CONFIG_PARTITION_TABLE_OFFSET`。 @@ -136,8 +137,9 @@ SubType 字段长度为 8 bit,内容与具体分区 Type 有关。目前,ESP - ``primary`` (0x00),即二级引导加载程序,位于 flash 的 {IDF_TARGET_CONFIG_BOOTLOADER_OFFSET_IN_FLASH} 地址处。工具会自动确定此子类型的适当大小和偏移量,因此为此子类型指定的任何大小或偏移量将被忽略。你可以将这些字段留空或使用 ``N/A`` 作为占位符。 - ``ota`` (0x01),是一个临时的引导加载程序分区,在 OTA 更新期间可用于下载新的引导加载程序镜像。工具会忽略此子类型的大小,你可以将其留空或使用 ``N/A``。你只能指定一个偏移量,或者将其留空,工具将根据先前使用的分区的偏移量进行计算。 + - ``recovery`` (0x02),这是用于安全执行引导加载程序 OTA 更新的恢复引导加载程序分区。``gen_esp32part.py`` 工具会自动确定该分区的地址和大小,因此可以将这些字段留空或使用 ``N/A`` 作为占位符。该分区地址必须与 Kconfig 选项定义的 eFuse 字段相匹配。如果正常的引导加载程序加载路径失败,则一级 (ROM) 引导加载程序会尝试加载 eFuse 字段指定地址的恢复分区。 - 引导加载程序的大小由 ``gen_esp32part.py`` 工具根据指定的 ``--offset`` (分区表偏移量) 和 ``--primary-partition-offset`` 参数计算。具体而言,引导加载程序的大小定义为 (:ref:`CONFIG_PARTITION_TABLE_OFFSET` - {IDF_TARGET_CONFIG_BOOTLOADER_OFFSET_IN_FLASH})。此计算的大小适用于引导加载程序的所有子类型。 + 引导加载程序类型的大小由 ``gen_esp32part.py`` 工具根据指定的 ``--offset`` (分区表偏移量)和 ``--primary-partition-offset`` 参数计算得出。具体来说,引导加载程序的大小定义为 (:ref:`CONFIG_PARTITION_TABLE_OFFSET` - {IDF_TARGET_CONFIG_BOOTLOADER_OFFSET_IN_FLASH})。此计算得出的大小适用于引导加载程序的所有子类型。 * 当 Type 定义为 ``partition_table`` 时,可以将 SubType 字段指定为: diff --git a/docs/zh_CN/api-guides/performance/size.rst b/docs/zh_CN/api-guides/performance/size.rst index 46b4521831e2..de8cd04b85c1 100644 --- a/docs/zh_CN/api-guides/performance/size.rst +++ b/docs/zh_CN/api-guides/performance/size.rst @@ -67,7 +67,7 @@ ESP-IDF 构建系统会编译项目和 ESP-IDF 中所有源文件,但只有程 .. note:: - 链接器映射文件由 GNU binutils 的链接器 ``ld`` 而非由 ESP-IDF 生成。本快速概览专从 ESP-IDF 构建系统的角度编写而成,建议自行搜索更多关于链接器映射文件格式的信息。 + 链接器映射文件由 GNU binutils 的链接器 ``ld`` 而非由 ESP-IDF 生成。本快速概览专从 ESP-IDF 构建系统的角度编写而成,建议自行搜索更多关于链接器映射文件格式的信息。 .. _reducing-overall-size: @@ -94,7 +94,7 @@ ESP-IDF 构建系统会编译项目和 ESP-IDF 中所有源文件,但只有程 .. note:: - 除了上述众多配置项之外,还有一些配置选项在更改为非默认设置时会增加二进制文件的大小,这些选项未在此列出。配置项的帮助文本中通常会阐明显著增加二进制文件大小的设置。 + 除了上述众多配置项之外,还有一些配置选项在更改为非默认设置时会增加二进制文件的大小,这些选项未在此列出。配置项的帮助文本中通常会阐明显著增加二进制文件大小的设置。 .. _size-targeted-optimizations: @@ -147,9 +147,9 @@ lwIP IPv4 - 如果不需要 IPv4 连接功能,将 :ref:`CONFIG_LWIP_IPV4` 设置为 ``false`` 可以减小 lwIP 的大小,使其成为仅支持 IPv6 的 TCP/IP 堆栈。 - .. note:: + .. note:: - 在禁用 IPv4 支持之前,请注意,仅支持 IPv6 的网络环境尚未普及,必须在本地网络中提供支持,例如,由互联网服务供应商提供支持,或使用受限制的本地网络设置。 + 在禁用 IPv4 支持之前,请注意,仅支持 IPv6 的网络环境尚未普及,必须在本地网络中提供支持,例如,由互联网服务供应商提供支持,或使用受限制的本地网络设置。 .. _picolibc-instead-of-newlib: @@ -224,21 +224,22 @@ MbedTLS 功能 .. important:: - **强烈建议不要禁用所有 mbedTLS 选项。** 仅在理解功能用途,并确定在应用程序中不需要此功能时,方可禁用相应选项。请特别注意以下两点: + **强烈建议不要禁用所有 mbedTLS 选项。** 仅在理解功能用途,并确定在应用程序中不需要此功能时,方可禁用相应选项。请特别注意以下两点: - - 确保设备连接的任何 TLS 服务器仍然可用。如果服务器由第三方或云服务控制,建议确保固件至少支持两种 TLS 密码套件,以防未来某次更新禁用了其中一种。 - - 确保连接设备的任何 TLS 客户端仍然可以使用支持/推荐的密码套件进行连接。请注意,未来版本的客户端操作系统可能会移除对某些功能的支持,因此建议启用多个支持的密码套件或算法以实现冗余。 + - 确保设备连接的任何 TLS 服务器仍然可用。如果服务器由第三方或云服务控制,建议确保固件至少支持两种 TLS 密码套件,以防未来某次更新禁用了其中一种。 + - 确保连接设备的任何 TLS 客户端仍然可以使用支持/推荐的密码套件进行连接。请注意,未来版本的客户端操作系统可能会移除对某些功能的支持,因此建议启用多个支持的密码套件或算法以实现冗余。 - 如果依赖于第三方客户端或服务器,请密切关注其有关支持的 TLS 功能的公告和变更。否则,当所支持功能变更时,{IDF_TARGET_NAME} 设备可能无法访问。 + 如果依赖于第三方客户端或服务器,请密切关注其有关支持的 TLS 功能的公告和变更。否则,当所支持功能变更时,{IDF_TARGET_NAME} 设备可能无法访问。 .. only:: CONFIG_ESP_ROM_HAS_MBEDTLS_CRYPTO_LIB - 启用配置选项 :ref:`CONFIG_MBEDTLS_USE_CRYPTO_ROM_IMPL` 时 mbedtls 使用由 ROM 提供的加密算法。 - 禁用配置选项 :ref:`CONFIG_MBEDTLS_USE_CRYPTO_ROM_IMPL` 时mbedtls 完全使用由 ESP-IDF 中提供的加密算法。这会导致二进制文件大小增加。 + 启用配置选项 :ref:`CONFIG_MBEDTLS_USE_CRYPTO_ROM_IMPL` 时 mbedtls 使用由 ROM 提供的加密算法。 + + 禁用配置选项 :ref:`CONFIG_MBEDTLS_USE_CRYPTO_ROM_IMPL` 时mbedtls 完全使用由 ESP-IDF 中提供的加密算法。这会导致二进制文件大小增加。 .. note:: - ESP-IDF 并未测试所有 mbedTLS 编译配置组合。如果发现某个组合无法编译或无法按预期执行,请在 `GitHub `_ 上报告详细信息。 + ESP-IDF 并未测试所有 mbedTLS 编译配置组合。如果发现某个组合无法编译或无法按预期执行,请在 `GitHub `_ 上报告详细信息。 虚拟文件系统 (VFS) @@@@@@@@@@@@@@@@@@@@@ @@ -264,9 +265,27 @@ MbedTLS 功能 @@@@@@ .. list:: + * 启用 :ref:`CONFIG_HEAP_TLSF_USE_ROM_IMPL` 可以将整个堆功能放置在 flash 中,从而减少 IRAM 使用和二进制文件大小。 :CONFIG_ESP_ROM_HAS_HEAP_TLSF: * 启用 :ref:`CONFIG_HEAP_TLSF_USE_ROM_IMPL` 可以通过链接 ROM 实现的 TLSF 库来减少 IRAM 使用和二进制文件大小。 + +.. only:: SOC_USB_SERIAL_JTAG_SUPPORTED + + 控制台 + @@@@@@ + + 对于支持 USB-Serial-JTAG 的目标芯片,默认情况下会同时启用 USB-Serial-JTAG 和 UART 控制台输出。如果只需要使用单个控制台,可以通过以下操作减少二进制文件大小和 RAM 使用量: + + 1. 禁用次要控制台:将 :ref:`CONFIG_ESP_CONSOLE_SECONDARY` 设置为 ``CONFIG_ESP_CONSOLE_SECONDARY_NONE``。 + 2. 将 :ref:`CONFIG_ESP_CONSOLE_UART` 设置为以下选项之一: + + * ``UART``:可减少约 2.5 KB 的二进制文件大小。 + * ``USB-Serial-JTAG``:可减少约 10 KB 的二进制文件大小和约 1.5 KB 的 DRAM 使用量。 + + 请注意,以上空间节省的前提条件是 UART/USB-Serial-JTAG 驱动代码未被应用程序引用。如果因为其他用途使用了这些驱动程序,则节省效果会有所降低。 + + 引导加载程序大小 ------------------------------ diff --git a/docs/zh_CN/api-reference/peripherals/camera_driver.rst b/docs/zh_CN/api-reference/peripherals/camera_driver.rst index 80c473f79307..543ed2cffa72 100644 --- a/docs/zh_CN/api-reference/peripherals/camera_driver.rst +++ b/docs/zh_CN/api-reference/peripherals/camera_driver.rst @@ -212,7 +212,8 @@ IRAM 安全 应用示例 -------- -* :example:`peripherals/camera/camera_dsi` 演示了如何使用 ``esp_driver_cam`` 组件从摄像头传感器捕获信号,并通过 DSI 接口将其显示在 ILI9881C LCD 屏幕上。 +* :example:`peripherals/camera/mipi_isp_dsi` 演示了如何使用 ``esp_driver_cam`` 组件从 MIPI CSI 摄像头传感器捕获信号,传入 ISP 模块,并通过 DSI 接口将其显示在 LCD 屏幕上。 +* :example:`peripherals/camera/dvp_isp_dsi` 演示了如何使用 ``esp_driver_cam`` 组件从 DVP 摄像头传感器捕获信号,传入 ISP 模块,并通过 DSI 接口将其显示在 LCD 屏幕上。 API 参考 -------- diff --git a/docs/zh_CN/api-reference/peripherals/pcnt.rst b/docs/zh_CN/api-reference/peripherals/pcnt.rst index acd8ed550548..8f67a551cc11 100644 --- a/docs/zh_CN/api-reference/peripherals/pcnt.rst +++ b/docs/zh_CN/api-reference/peripherals/pcnt.rst @@ -104,6 +104,10 @@ PCNT 单元和通道分别用 :cpp:type:`pcnt_unit_handle_t` 与 :cpp:type:`pcnt pcnt_channel_handle_t pcnt_chan = NULL; ESP_ERROR_CHECK(pcnt_new_channel(pcnt_unit, &chan_config, &pcnt_chan)); +.. note:: + + PCNT 中涉及到的 GPIO 都可以在初始化完 PCNT 后, 通过 :cpp:func:`gpio_pullup_en` 和 :cpp:func:`gpio_pullup_dis` 等函数,重新进行上下拉等配置。 + .. _pcnt-setup-channel-actions: 设置通道操作 diff --git a/docs/zh_CN/api-reference/system/bootloader_image_format.rst b/docs/zh_CN/api-reference/system/bootloader_image_format.rst index b77b6c219aa2..1f7073bf1d1c 100644 --- a/docs/zh_CN/api-reference/system/bootloader_image_format.rst +++ b/docs/zh_CN/api-reference/system/bootloader_image_format.rst @@ -64,6 +64,7 @@ * ``magic_byte``:esp_bootloader_desc 结构体的魔术字节 * ``reserved``:保留供 IDF 未来使用 + * ``secure_version``:引导加载程序防回滚功能使用的安全版本,请参阅:ref:`CONFIG_BOOTLOADER_ANTI_ROLLBACK_ENABLE`。 * ``version``:引导加载程序版本,参见 :ref:`CONFIG_BOOTLOADER_PROJECT_VER` * ``idf_ver``:IDF 版本。[#f1]_ * ``date`` 和 ``time``:编译日期和时间 diff --git a/docs/zh_CN/migration-guides/release-5.x/5.4/wifi.rst b/docs/zh_CN/migration-guides/release-5.x/5.4/wifi.rst index ed9be5db8df9..0625e88b7230 100644 --- a/docs/zh_CN/migration-guides/release-5.x/5.4/wifi.rst +++ b/docs/zh_CN/migration-guides/release-5.x/5.4/wifi.rst @@ -8,6 +8,7 @@ Wi-Fi 扫描和连接 ------------------------ 以下类型已被更改: + - :component_file:`esp_wifi/include/esp_wifi_he_types.h` - :cpp:struct:`esp_wifi_htc_omc_t` 中: @@ -19,6 +20,3 @@ Wi-Fi 扫描和连接 - :cpp:struct:`wifi_ap_record_t` 中: - ``bandwidth`` 的类型从 ``uint8_t`` 更改为 ``wifi_bandwidth_t`` - - - diff --git a/examples/bluetooth/bluedroid/classic_bt/hfp_hf/README.md b/examples/bluetooth/bluedroid/classic_bt/hfp_hf/README.md index ab8040e276dc..9ed38dab29c0 100644 --- a/examples/bluetooth/bluedroid/classic_bt/hfp_hf/README.md +++ b/examples/bluetooth/bluedroid/classic_bt/hfp_hf/README.md @@ -64,6 +64,18 @@ PCM Signal supports three configurations in menuconfig: PCM Role, PCM Polar and - The default configuration is `Stereo Mode`, you can change the PCM Channel mode in `menuconfig` path: `Component config --> Bluetooth --> Controller Options --> PCM Signal Configurations --> PCM Signal Configurations: Role, Polar and Channel Mode(Stereo/Mono) --> Channel Mode(Stereo/Mono)` +### Special Configurations for PBA Client + +To use PBA Client function, we need to enable PBA Client in `menuconfig` path: `Component config --> Bluetooth --> Bluedroid Options --> Classic Bluetooth --> Classic BT PBA Client`, this example already enable PBA Client by `sdkconfig.defaults`. + +Step to initialize PBA Client connection: + +- Register user callback: `esp_pbac_register_callback(bt_app_pbac_cb)` +- Initialize PBA Client API: `esp_pbac_init()` +- Connect to peer device ... +- Call `esp_pbac_connect(peer_addr, supported_features, 0)`, this will initiate service discover and try to connect to PBA Server. +- After the operation done, whether success or not, we will receive a `ESP_PBAC_CONNECTION_STATE_EVT` event in user callback. + ### Codec Choice ESP32 supports two types of codec for HFP audio data: `CVSD` and `mSBC`. diff --git a/examples/bluetooth/bluedroid/classic_bt/hfp_hf/main/CMakeLists.txt b/examples/bluetooth/bluedroid/classic_bt/hfp_hf/main/CMakeLists.txt index 728e74c35295..d91b3279d0b6 100644 --- a/examples/bluetooth/bluedroid/classic_bt/hfp_hf/main/CMakeLists.txt +++ b/examples/bluetooth/bluedroid/classic_bt/hfp_hf/main/CMakeLists.txt @@ -2,6 +2,7 @@ idf_component_register(SRCS "app_hf_msg_set.c" "bt_app_core.c" "bt_app_hf.c" "gpio_pcm_config.c" + "bt_app_pbac.c" "main.c" PRIV_REQUIRES bt nvs_flash esp_driver_gpio console esp_ringbuf INCLUDE_DIRS ".") diff --git a/examples/bluetooth/bluedroid/classic_bt/hfp_hf/main/bt_app_hf.c b/examples/bluetooth/bluedroid/classic_bt/hfp_hf/main/bt_app_hf.c index 26f2af87d854..65169f183289 100644 --- a/examples/bluetooth/bluedroid/classic_bt/hfp_hf/main/bt_app_hf.c +++ b/examples/bluetooth/bluedroid/classic_bt/hfp_hf/main/bt_app_hf.c @@ -17,6 +17,7 @@ #include "esp_bt_device.h" #include "esp_gap_bt_api.h" #include "esp_hf_client_api.h" +#include "esp_pbac_api.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/queue.h" @@ -244,6 +245,9 @@ void bt_app_hf_client_cb(esp_hf_client_cb_event_t event, esp_hf_client_cb_param_ param->conn_stat.peer_feat, param->conn_stat.chld_feat); memcpy(peer_addr,param->conn_stat.remote_bda,ESP_BD_ADDR_LEN); + if (param->conn_stat.state == ESP_HF_CLIENT_CONNECTION_STATE_SLC_CONNECTED) { + esp_pbac_connect(peer_addr); + } break; } diff --git a/examples/bluetooth/bluedroid/classic_bt/hfp_hf/main/bt_app_pbac.c b/examples/bluetooth/bluedroid/classic_bt/hfp_hf/main/bt_app_pbac.c new file mode 100644 index 000000000000..09ac19811c9a --- /dev/null +++ b/examples/bluetooth/bluedroid/classic_bt/hfp_hf/main/bt_app_pbac.c @@ -0,0 +1,86 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include "esp_log.h" +#include "esp_bt.h" +#include "esp_pbac_api.h" +#include "bt_app_core.h" +#include "bt_app_pbac.h" + +#define BT_PBAC_TAG "BT_PBAC" + +esp_pbac_conn_hdl_t pba_conn_handle; + +void bt_app_pbac_cb(esp_pbac_event_t event, esp_pbac_param_t *param) +{ + switch (event) + { + case ESP_PBAC_CONNECTION_STATE_EVT: + ESP_LOGI(BT_PBAC_TAG, "PBA client connection event, state: %s, reason: 0x%x", (param->conn_stat.connected ? "Connected" : "Disconnected"), param->conn_stat.reason); + ESP_LOGI(BT_PBAC_TAG, "Peer supported repositories: 0x%x, supported features: 0x%lx", param->conn_stat.peer_supported_repo, param->conn_stat.peer_supported_feat); + if (param->conn_stat.connected) { + pba_conn_handle = param->conn_stat.handle; + /* set phone book to "telecom" folder, just to test set phone book function */ + esp_pbac_set_phone_book(pba_conn_handle, ESP_PBAC_SET_PHONE_BOOK_FLAGS_DOWN, "telecom"); + } + break; + case ESP_PBAC_PULL_PHONE_BOOK_RESPONSE_EVT: + /* if multiple PBA connection, we should check param->pull_phone_book_rsp.handle */ + ESP_LOGI(BT_PBAC_TAG, "PBA client pull phone book response, handle:%d, result: 0x%x", param->pull_phone_book_rsp.handle, param->pull_phone_book_rsp.result); + if (param->pull_phone_book_rsp.result == ESP_PBAC_SUCCESS && param->pull_phone_book_rsp.data_len > 0) { + printf("%.*s\n", param->pull_phone_book_rsp.data_len, param->pull_phone_book_rsp.data); + /* copy data to other buff before return, if phone book size is too large, it will be sent in multiple response event */ + } + if (param->pull_phone_book_rsp.final) { + ESP_LOGI(BT_PBAC_TAG, "PBA client pull phone book final response"); + /* pull phone book done, now we can perform other operation */ + if (param->pull_phone_book_rsp.result == ESP_PBAC_SUCCESS && param->pull_phone_book_rsp.include_phone_book_size) { + ESP_LOGI(BT_PBAC_TAG, "Phone Book Size:%d", param->pull_phone_book_rsp.phone_book_size); + esp_pbac_pull_phone_book_app_param_t app_param = {0}; + app_param.include_property_selector = 1; + /* property bit mask, filter out photo, refer to Phone Book Access Profile */ + app_param.property_selector = 0xFFFFFFF7; + /* pull again, without 'max_list_count = 0', then we can get the entire phone book */ + esp_pbac_pull_phone_book(pba_conn_handle, "telecom/pb.vcf", &app_param); + } + } + break; + case ESP_PBAC_SET_PHONE_BOOK_RESPONSE_EVT: + ESP_LOGI(BT_PBAC_TAG, "PBA client set phone book response, handle:%d, result: 0x%x", param->set_phone_book_rsp.handle, param->set_phone_book_rsp.result); + /* done, set phone book response will always be a final response */ + if (param->set_phone_book_rsp.result == ESP_PBAC_SUCCESS) { + esp_pbac_pull_phone_book_app_param_t app_param = {0}; + app_param.include_max_list_count = 1; + /* set max_list_count to zero, then we can get phone book size in peer response */ + app_param.max_list_count = 0; + /* pull phone book use a absolute path; if no app param, we can pass a NULL to API */ + esp_pbac_pull_phone_book(pba_conn_handle, "telecom/pb.vcf", &app_param); + } + break; + case ESP_PBAC_PULL_VCARD_LISTING_RESPONSE_EVT: + ESP_LOGI(BT_PBAC_TAG, "PBA client pull vCard listing response, handle:%d, result: 0x%x", param->pull_vcard_listing_rsp.handle, param->pull_vcard_listing_rsp.result); + if (param->pull_vcard_listing_rsp.result == ESP_PBAC_SUCCESS) { + printf("%.*s\n", param->pull_vcard_listing_rsp.data_len, param->pull_vcard_listing_rsp.data); + } + if (param->pull_vcard_listing_rsp.final) { + ESP_LOGI(BT_PBAC_TAG, "PBA client pull vCard listing final response"); + } + break; + case ESP_PBAC_PULL_VCARD_ENTRY_RESPONSE_EVT: + ESP_LOGI(BT_PBAC_TAG, "PBA client pull vCard entry response, handle:%d, result: 0x%x", param->pull_vcard_entry_rsp.handle, param->pull_vcard_entry_rsp.result); + if (param->pull_vcard_entry_rsp.result == ESP_PBAC_SUCCESS) { + printf("%.*s\n", param->pull_vcard_entry_rsp.data_len, param->pull_vcard_entry_rsp.data); + } + if (param->pull_vcard_entry_rsp.final) { + ESP_LOGI(BT_PBAC_TAG, "PBA client pull vCard entry final response"); + } + break; + default: + break; + } +} diff --git a/examples/bluetooth/bluedroid/classic_bt/hfp_hf/main/bt_app_pbac.h b/examples/bluetooth/bluedroid/classic_bt/hfp_hf/main/bt_app_pbac.h new file mode 100644 index 000000000000..2e0b87b53be9 --- /dev/null +++ b/examples/bluetooth/bluedroid/classic_bt/hfp_hf/main/bt_app_pbac.h @@ -0,0 +1,11 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include "esp_bt.h" +#include "esp_pbac_api.h" + +void bt_app_pbac_cb(esp_pbac_event_t event, esp_pbac_param_t *param); diff --git a/examples/bluetooth/bluedroid/classic_bt/hfp_hf/main/main.c b/examples/bluetooth/bluedroid/classic_bt/hfp_hf/main/main.c index 984695aaaac1..3e40498143da 100644 --- a/examples/bluetooth/bluedroid/classic_bt/hfp_hf/main/main.c +++ b/examples/bluetooth/bluedroid/classic_bt/hfp_hf/main/main.c @@ -21,10 +21,12 @@ #include "esp_bt_device.h" #include "esp_gap_bt_api.h" #include "esp_hf_client_api.h" +#include "esp_pbac_api.h" #include "bt_app_hf.h" #include "gpio_pcm_config.h" #include "esp_console.h" #include "app_hf_msg_set.h" +#include "bt_app_pbac.h" esp_bd_addr_t peer_addr = {0}; static char peer_bdname[ESP_BT_GAP_MAX_BDNAME_LEN + 1]; @@ -251,6 +253,8 @@ static void bt_hf_client_hdl_stack_evt(uint16_t event, void *p_param) esp_bt_gap_register_callback(esp_bt_gap_cb); esp_hf_client_register_callback(bt_app_hf_client_cb); esp_hf_client_init(); + esp_pbac_register_callback(bt_app_pbac_cb); + esp_pbac_init(); #if (CONFIG_EXAMPLE_SSP_ENABLED == true) /* Set default parameters for Secure Simple Pairing */ diff --git a/examples/bluetooth/bluedroid/classic_bt/hfp_hf/sdkconfig.defaults b/examples/bluetooth/bluedroid/classic_bt/hfp_hf/sdkconfig.defaults index 351210d4f831..643ce88d5da6 100644 --- a/examples/bluetooth/bluedroid/classic_bt/hfp_hf/sdkconfig.defaults +++ b/examples/bluetooth/bluedroid/classic_bt/hfp_hf/sdkconfig.defaults @@ -8,3 +8,4 @@ CONFIG_BT_BLUEDROID_ENABLED=y CONFIG_BT_CLASSIC_ENABLED=y CONFIG_BT_HFP_ENABLE=y CONFIG_BT_HFP_CLIENT_ENABLE=y +CONFIG_BT_PBAC_ENABLED=y diff --git a/examples/openthread/ot_ci_function.py b/examples/openthread/ot_ci_function.py index d7883a5ee5bd..adf19683e9c8 100644 --- a/examples/openthread/ot_ci_function.py +++ b/examples/openthread/ot_ci_function.py @@ -119,14 +119,15 @@ def joinWiFiNetwork(dut:IdfDut, wifi:wifi_parameter) -> Tuple[str, int]: tmp = get_ouput_string(dut, command, 10) if 'sta ip' in str(tmp): ip_address = re.findall(r'sta ip: (\w+.\w+.\w+.\w+),', str(tmp))[0] + wait(dut, 2) execute_command(dut, 'wifi state') - if dut.expect('\nconnected\r', timeout=5): + if dut.expect('connected', timeout=5): return ip_address, order raise Exception(f'{dut} connect wifi {str(wifi.ssid)} with password {str(wifi.psk)} fail') def getDeviceRole(dut:IdfDut) -> str: - clean_buffer(dut) + wait(dut, 1) execute_command(dut, 'state') role = dut.expect(r'\W+(\w+)\W+Done', timeout=5)[1].decode() print(role) @@ -139,7 +140,6 @@ def changeDeviceRole(dut:IdfDut, role:str) -> None: def getDataset(dut:IdfDut) -> str: - clean_buffer(dut) execute_command(dut, 'dataset active -x') dut_data = dut.expect(r'\n(\w+)\r', timeout=5)[1].decode() return str(dut_data) @@ -152,7 +152,6 @@ def init_thread(dut:IdfDut) -> None: def reset_thread(dut:IdfDut) -> None: - clean_buffer(dut) execute_command(dut, 'factoryreset') dut.expect('OpenThread attached to netif', timeout=20) dut.expect('>', timeout=10) @@ -163,7 +162,6 @@ def reset_thread(dut:IdfDut) -> None: # get the mleid address of the thread def get_mleid_addr(dut:IdfDut) -> str: dut_adress = '' - clean_buffer(dut) execute_command(dut, 'ipaddr mleid') dut_adress = dut.expect(r'\n((?:\w+:){7}\w+)\r', timeout=5)[1].decode() return dut_adress @@ -172,7 +170,6 @@ def get_mleid_addr(dut:IdfDut) -> str: # get the rloc address of the thread def get_rloc_addr(dut:IdfDut) -> str: dut_adress = '' - clean_buffer(dut) execute_command(dut, 'ipaddr rloc') dut_adress = dut.expect(r'\n((?:\w+:){7}\w+)\r', timeout=5)[1].decode() return dut_adress @@ -181,7 +178,6 @@ def get_rloc_addr(dut:IdfDut) -> str: # get the linklocal address of the thread def get_linklocal_addr(dut:IdfDut) -> str: dut_adress = '' - clean_buffer(dut) execute_command(dut, 'ipaddr linklocal') dut_adress = dut.expect(r'\n((?:\w+:){7}\w+)\r', timeout=5)[1].decode() return dut_adress @@ -192,7 +188,6 @@ def get_global_unicast_addr(dut:IdfDut, br:IdfDut) -> str: dut_adress = '' clean_buffer(br) omrprefix = get_omrprefix(br) - clean_buffer(dut) execute_command(dut, 'ipaddr') dut_adress = dut.expect(r'(%s(?:\w+:){3}\w+)\r' % str(omrprefix), timeout=5)[1].decode() return dut_adress @@ -537,33 +532,29 @@ def decimal_to_hex(decimal_str:str) -> str: def get_omrprefix(br:IdfDut) -> str: - clean_buffer(br) execute_command(br, 'br omrprefix') omrprefix = br.expect(r'Local: ((?:\w+:){4}):/\d+\r', timeout=5)[1].decode() return str(omrprefix) def get_onlinkprefix(br:IdfDut) -> str: - clean_buffer(br) execute_command(br, 'br onlinkprefix') onlinkprefix = br.expect(r'Local: ((?:\w+:){4}):/\d+\r', timeout=5)[1].decode() return str(onlinkprefix) def get_nat64prefix(br:IdfDut) -> str: - clean_buffer(br) execute_command(br, 'br nat64prefix') nat64prefix = br.expect(r'Local: ((?:\w+:){6}):/\d+', timeout=5)[1].decode() return str(nat64prefix) def execute_command(dut:IdfDut, command:str) -> None: + clean_buffer(dut) dut.write(command) - dut.expect(command, timeout=3) def get_ouput_string(dut:IdfDut, command:str, wait_time:int) -> str: - clean_buffer(dut) execute_command(dut, command) tmp = dut.expect(pexpect.TIMEOUT, timeout=wait_time) clean_buffer(dut) diff --git a/examples/openthread/pytest_otbr.py b/examples/openthread/pytest_otbr.py index 0be9172fefdc..3f851b536443 100644 --- a/examples/openthread/pytest_otbr.py +++ b/examples/openthread/pytest_otbr.py @@ -618,7 +618,6 @@ def test_ot_sleepy_device(dut: Tuple[IdfDut, IdfDut]) -> None: assert not bool(fail_info.search(str(info))) output = sleepy_device.expect(pexpect.TIMEOUT, timeout=20) assert not bool(fail_info.search(str(output))) - ocf.clean_buffer(sleepy_device) ocf.execute_command(leader, 'factoryreset') output = sleepy_device.expect(pexpect.TIMEOUT, timeout=5) assert not bool(fail_info.search(str(output))) @@ -648,7 +647,6 @@ def test_basic_startup(dut: Tuple[IdfDut, IdfDut]) -> None: try: ocf.init_thread(br) time.sleep(3) - ocf.clean_buffer(br) ocf.execute_command(br, 'ifconfig up') br.expect('Done', timeout=5) ocf.execute_command(br, 'thread start') diff --git a/examples/peripherals/.build-test-rules.yml b/examples/peripherals/.build-test-rules.yml index 160fdd33fe19..63e817e7c898 100644 --- a/examples/peripherals/.build-test-rules.yml +++ b/examples/peripherals/.build-test-rules.yml @@ -38,7 +38,14 @@ examples/peripherals/analog_comparator: - esp_driver_gpio - esp_driver_ana_cmpr -examples/peripherals/camera/camera_dsi: +examples/peripherals/camera/dvp_isp_dsi: + disable: + - if: SOC_ISP_DVP_SUPPORTED != 1 or SOC_MIPI_DSI_SUPPORTED != 1 + depends_components: + - esp_lcd + - esp_driver_cam + +examples/peripherals/camera/mipi_isp_dsi: disable: - if: SOC_MIPI_CSI_SUPPORTED != 1 or SOC_MIPI_DSI_SUPPORTED != 1 depends_components: diff --git a/examples/peripherals/camera/camera_dsi/components/sensor_init/include/example_sensor_init.h b/examples/peripherals/camera/camera_dsi/components/sensor_init/include/example_sensor_init.h deleted file mode 100644 index c2e1db3d3113..000000000000 --- a/examples/peripherals/camera/camera_dsi/components/sensor_init/include/example_sensor_init.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: Apache-2.0 - */ - -#pragma once - -#include "driver/i2c_master.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * @brief SCCB Interface and Sensor Init - * - * @param[in] i2c_port I2C port - * @param[out] out_i2c_bus_handle I2C bus handle - */ -void example_sensor_init(int i2c_port, i2c_master_bus_handle_t *out_i2c_bus_handle); - -#ifdef __cplusplus -} -#endif diff --git a/examples/peripherals/camera/camera_dsi/main/idf_component.yml b/examples/peripherals/camera/camera_dsi/main/idf_component.yml deleted file mode 100644 index 363caa48c92a..000000000000 --- a/examples/peripherals/camera/camera_dsi/main/idf_component.yml +++ /dev/null @@ -1,7 +0,0 @@ -dependencies: - idf: - version: ">=5.3.0" - dsi_init: - path: ${IDF_PATH}/examples/peripherals/camera/camera_dsi/components/dsi_init - sensor_init: - path: ${IDF_PATH}/examples/peripherals/camera/camera_dsi/components/sensor_init diff --git a/examples/peripherals/camera/camera_dsi/components/dsi_init/CMakeLists.txt b/examples/peripherals/camera/common_components/dsi_init/CMakeLists.txt similarity index 100% rename from examples/peripherals/camera/camera_dsi/components/dsi_init/CMakeLists.txt rename to examples/peripherals/camera/common_components/dsi_init/CMakeLists.txt diff --git a/examples/peripherals/camera/camera_dsi/components/dsi_init/Kconfig.projbuild b/examples/peripherals/camera/common_components/dsi_init/Kconfig.projbuild similarity index 100% rename from examples/peripherals/camera/camera_dsi/components/dsi_init/Kconfig.projbuild rename to examples/peripherals/camera/common_components/dsi_init/Kconfig.projbuild diff --git a/examples/peripherals/camera/camera_dsi/components/dsi_init/example_dsi_init.c b/examples/peripherals/camera/common_components/dsi_init/example_dsi_init.c similarity index 100% rename from examples/peripherals/camera/camera_dsi/components/dsi_init/example_dsi_init.c rename to examples/peripherals/camera/common_components/dsi_init/example_dsi_init.c diff --git a/examples/peripherals/camera/camera_dsi/components/dsi_init/idf_component.yml b/examples/peripherals/camera/common_components/dsi_init/idf_component.yml similarity index 100% rename from examples/peripherals/camera/camera_dsi/components/dsi_init/idf_component.yml rename to examples/peripherals/camera/common_components/dsi_init/idf_component.yml diff --git a/examples/peripherals/camera/camera_dsi/components/dsi_init/include/example_dsi_init.h b/examples/peripherals/camera/common_components/dsi_init/include/example_dsi_init.h similarity index 100% rename from examples/peripherals/camera/camera_dsi/components/dsi_init/include/example_dsi_init.h rename to examples/peripherals/camera/common_components/dsi_init/include/example_dsi_init.h diff --git a/examples/peripherals/camera/camera_dsi/components/dsi_init/include/example_dsi_init_config.h b/examples/peripherals/camera/common_components/dsi_init/include/example_dsi_init_config.h similarity index 100% rename from examples/peripherals/camera/camera_dsi/components/dsi_init/include/example_dsi_init_config.h rename to examples/peripherals/camera/common_components/dsi_init/include/example_dsi_init_config.h diff --git a/examples/peripherals/camera/camera_dsi/components/sensor_init/CMakeLists.txt b/examples/peripherals/camera/common_components/sensor_init/CMakeLists.txt similarity index 72% rename from examples/peripherals/camera/camera_dsi/components/sensor_init/CMakeLists.txt rename to examples/peripherals/camera/common_components/sensor_init/CMakeLists.txt index 6fb9927a8905..474d6f8187c2 100644 --- a/examples/peripherals/camera/camera_dsi/components/sensor_init/CMakeLists.txt +++ b/examples/peripherals/camera/common_components/sensor_init/CMakeLists.txt @@ -1,3 +1,4 @@ idf_component_register(SRCS "example_sensor_init.c" INCLUDE_DIRS "include" + REQUIRES esp_cam_sensor ) diff --git a/examples/peripherals/camera/camera_dsi/components/sensor_init/example_sensor_init.c b/examples/peripherals/camera/common_components/sensor_init/example_sensor_init.c similarity index 85% rename from examples/peripherals/camera/camera_dsi/components/sensor_init/example_sensor_init.c rename to examples/peripherals/camera/common_components/sensor_init/example_sensor_init.c index 89346eee925f..77a6f6636ce0 100644 --- a/examples/peripherals/camera/camera_dsi/components/sensor_init/example_sensor_init.c +++ b/examples/peripherals/camera/common_components/sensor_init/example_sensor_init.c @@ -13,36 +13,32 @@ #include "driver/i2c_master.h" #include "esp_sccb_intf.h" #include "esp_sccb_i2c.h" -#include "esp_cam_sensor.h" #include "esp_cam_sensor_detect.h" #include "example_sensor_init.h" #include "example_sensor_init_config.h" static const char *TAG = "sensor_init"; -void example_sensor_init(int i2c_port, i2c_master_bus_handle_t *out_i2c_bus_handle) +void example_sensor_init(example_sensor_config_t *sensor_config, i2c_master_bus_handle_t *out_i2c_bus_handle) { esp_err_t ret = ESP_FAIL; //---------------I2C Init------------------// i2c_master_bus_config_t i2c_bus_conf = { .clk_source = I2C_CLK_SRC_DEFAULT, - .sda_io_num = EXAMPLE_CAM_SCCB_SDA_IO, - .scl_io_num = EXAMPLE_CAM_SCCB_SCL_IO, - .i2c_port = i2c_port, + .sda_io_num = sensor_config->i2c_sda_io_num, + .scl_io_num = sensor_config->i2c_scl_io_num, + .i2c_port = sensor_config->i2c_port_num, .flags.enable_internal_pullup = true, }; i2c_master_bus_handle_t i2c_bus_handle = NULL; ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_conf, &i2c_bus_handle)); //---------------SCCB Init------------------// - esp_sccb_io_handle_t sccb_io_handle = NULL; esp_cam_sensor_config_t cam_config = { - .sccb_handle = sccb_io_handle, .reset_pin = -1, .pwdn_pin = -1, .xclk_pin = -1, - .sensor_port = ESP_CAM_SENSOR_MIPI_CSI, }; esp_cam_sensor_device_t *cam = NULL; @@ -54,9 +50,11 @@ void example_sensor_init(int i2c_port, i2c_master_bus_handle_t *out_i2c_bus_hand }; ESP_ERROR_CHECK(sccb_new_i2c_io(i2c_bus_handle, &i2c_config, &cam_config.sccb_handle)); + cam_config.sensor_port = p->port; + cam = (*(p->detect))(&cam_config); if (cam) { - if (p->port != ESP_CAM_SENSOR_MIPI_CSI) { + if (p->port != sensor_config->port) { ESP_LOGE(TAG, "detect a camera sensor with mismatched interface"); return; } @@ -79,8 +77,8 @@ void example_sensor_init(int i2c_port, i2c_master_bus_handle_t *out_i2c_bus_hand esp_cam_sensor_format_t *cam_cur_fmt = NULL; for (int i = 0; i < cam_fmt_array.count; i++) { - if (!strcmp(parray[i].name, EXAMPLE_CAM_FORMAT)) { - cam_cur_fmt = (esp_cam_sensor_format_t *) & (parray[i].name); + if (!strcmp(parray[i].name, sensor_config->format_name)) { + cam_cur_fmt = (esp_cam_sensor_format_t *) & (parray[i]); } } diff --git a/examples/peripherals/camera/camera_dsi/components/sensor_init/idf_component.yml b/examples/peripherals/camera/common_components/sensor_init/idf_component.yml similarity index 54% rename from examples/peripherals/camera/camera_dsi/components/sensor_init/idf_component.yml rename to examples/peripherals/camera/common_components/sensor_init/idf_component.yml index fbd8f4b02866..1881aa5e9956 100644 --- a/examples/peripherals/camera/camera_dsi/components/sensor_init/idf_component.yml +++ b/examples/peripherals/camera/common_components/sensor_init/idf_component.yml @@ -1,4 +1,4 @@ dependencies: - espressif/esp_cam_sensor: "^0.5.1" + espressif/esp_cam_sensor: "^0.6.1" idf: version: ">=5.3.0" diff --git a/examples/peripherals/camera/common_components/sensor_init/include/example_sensor_init.h b/examples/peripherals/camera/common_components/sensor_init/include/example_sensor_init.h new file mode 100644 index 000000000000..866cb3dd95fe --- /dev/null +++ b/examples/peripherals/camera/common_components/sensor_init/include/example_sensor_init.h @@ -0,0 +1,37 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "driver/i2c_master.h" +#include "esp_cam_sensor.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Configuration of SCCB interface and sensor + */ +typedef struct { + int i2c_port_num; /* SCCB: i2c port */ + int i2c_sda_io_num; /* SCCB: i2c SDA IO number */ + int i2c_scl_io_num; /* SCCB: i2c SCL IO number */ + esp_cam_sensor_port_t port; /* Sensor: interface of the camera sensor */ + const char *format_name; /* Sensor: format to be set for the camera sensor */ +} example_sensor_config_t; + +/** + * @brief SCCB Interface and Sensor Init + * + * @param[in] sensor_config Camera sensor configuration + * @param[out] out_i2c_bus_handle I2C bus handle + */ +void example_sensor_init(example_sensor_config_t *sensor_config, i2c_master_bus_handle_t *out_i2c_bus_handle); + +#ifdef __cplusplus +} +#endif diff --git a/examples/peripherals/camera/camera_dsi/main/example_config.h b/examples/peripherals/camera/common_components/sensor_init/include/example_sensor_init_config.h similarity index 52% rename from examples/peripherals/camera/camera_dsi/main/example_config.h rename to examples/peripherals/camera/common_components/sensor_init/include/example_sensor_init_config.h index 8e5ad61cdff4..dd3acc47201b 100644 --- a/examples/peripherals/camera/camera_dsi/main/example_config.h +++ b/examples/peripherals/camera/common_components/sensor_init/include/example_sensor_init_config.h @@ -10,9 +10,7 @@ extern "C" { #endif -#define EXAMPLE_RGB565_BITS_PER_PIXEL 16 -#define EXAMPLE_MIPI_IDI_CLOCK_RATE (50000000) -#define EXAMPLE_MIPI_CSI_LANE_BITRATE_MBPS 200 //line_rate = pclk * 4 +#define EXAMPLE_CAM_SCCB_FREQ (100000) #ifdef __cplusplus } diff --git a/examples/peripherals/camera/camera_dsi/CMakeLists.txt b/examples/peripherals/camera/dvp_isp_dsi/CMakeLists.txt similarity index 94% rename from examples/peripherals/camera/camera_dsi/CMakeLists.txt rename to examples/peripherals/camera/dvp_isp_dsi/CMakeLists.txt index 30767da93357..e4accb114202 100644 --- a/examples/peripherals/camera/camera_dsi/CMakeLists.txt +++ b/examples/peripherals/camera/dvp_isp_dsi/CMakeLists.txt @@ -5,4 +5,4 @@ cmake_minimum_required(VERSION 3.16) include($ENV{IDF_PATH}/tools/cmake/project.cmake) # "Trim" the build. Include the minimal set of components, main, and anything it depends on. idf_build_set_property(MINIMAL_BUILD ON) -project(camera_dsi) +project(dvp_isp_dsi) diff --git a/examples/peripherals/camera/dvp_isp_dsi/README.md b/examples/peripherals/camera/dvp_isp_dsi/README.md new file mode 100644 index 000000000000..dda9ad49a58b --- /dev/null +++ b/examples/peripherals/camera/dvp_isp_dsi/README.md @@ -0,0 +1,154 @@ +| Supported Targets | ESP32-P4 | +| ----------------- | -------- | + + +# DVP Camera display via DSI example + +## Overview + +This example demonstrates how to use the esp_driver_cam component to capture DVP camera sensor signals and display it via DSI interface. This example will auto-detect camera sensors via [ESP camera sensor driver](https://components.espressif.com/components/espressif/esp_cam_sensor) and capture camera sensor signals via DVP interface and display it via DSI interface. + +## Usage + +The subsections below give only absolutely necessary information. For full steps to configure ESP-IDF and use it to build and run projects, see [ESP-IDF Getting Started](https://docs.espressif.com/projects/esp-idf/en/latest/get-started/index.html#get-started). + + +### Hardware Required + +- OV2640 camera sensor, or other camera sensors with DVP port that can output raw format color data +- EK79007 or ILI9881C LCD screen +- ESP32P4 devkit + +**Note:** For EK79007 you will need to connect following pins: +- 5V - 5V +- GND - GND +- RST_LCD - 3V3 + +You can also connect camera sensors and LCD screens from other vendors to the ESP chip, you can find corresponding camera or LCD drivers from [ESP Component Registry](https://components.espressif.com), or design your own customized drivers. + + + GND GND + ┌────────────────────────────────────────────────┐ ┌─────────────────────────────────────────────────────────┐ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ ┌───────────────┴─────────────┴──────────────────┐ │ + │ │ │ ┌──────────┴───────────┐ + │ │ │ DSI DATA 1P │ │ + │ │ ├───────────────────────────┤ │ + ┌───────────┴─────────┐ │ │ │ │ + │ │ │ │ DSI DATA 1N │ │ + │ │ │ ├───────────────────────────┤ │ + │ │ XCLK │ ESP32-P4 │ │ │ + │ DVP Camera ├──────────────────────┤ │ DSI CLK N │ LCD Screen │ + │ │ │ ├───────────────────────────┤ │ + │ │ D0~7 │ │ │ │ + │ ├──────────────────────┤ │ DSI CLK P │ │ + │ │ │ ├───────────────────────────┤ │ + │ │ PCLK │ │ │ │ + │ ├──────────────────────┤ │ DSI DATA 0P │ │ + │ │ │ ├───────────────────────────┤ │ + │ │ VSYNC │ │ │ │ + │ ├──────────────────────┤ │ DSI DATA 0N │ │ + │ │ │ ├───────────────────────────┤ │ + │ │ DE (HREF) │ │ │ │ + │ ├──────────────────────┤ │ └──────────────────────┘ + │ │ │ │ + └───────┬──┬──────────┘ │ │ + │ │ I2C SCL │ │ + │ └─────────────────────────────────┤ │ + │ I2C SDA │ │ + └────────────────────────────────────┤ │ + └────────────────────────────────────────────────┘ + + +### Set Chip Target + +First of all, your target must be supported by both: + +- **By your ESP-IDF version**: For the full list of supported targets, run: + ``` + idf.py --list-targets + ``` +- **By this example**: For the full list of supported targets, refer to the supported targets table at the top of this README. + +After you make sure that your target is supported, go to your example project directory and [set the chip target](https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/tools/idf-py.html#select-the-target-chip-set-target): + +``` +idf.py set-target +``` + +For example, to set esp32-P4 as the chip target, run: + +``` +idf.py set-target esp32p4 +``` + + +### Configure the Project + +For information about Kconfig options, see [Project Configuration](https://docs.espressif.com/projects/esp-idf/en/latest/api-reference/kconfig.html) > _Name of relevant section(s)_. + +To conveniently check or modify Kconfig options for this example in a project configuration menu, run: + +``` +idf.py menuconfig +``` + +``` +Set CONFIG_CAMERA_OV2640 to y +``` + +Remember to select the LCD screen model and set corresponding correct horizontal/vertical resolution in ``menuconfig`` > ``Example DSI Configuration``. + +Available options for the camera sensor output horizontal/vertical resolution can be seen in ``menuconfig`` > ``Example Configuration``. Note that the horizontal resolution for the camera should be the same as the LCD screen horizontal resolution. + + +### Build and Flash + +Execute the following command to build the project, flash it to your development board, and run the monitor tool to view the serial output: + +``` +idf.py build flash monitor +``` + +This command can be reduced to `idf.py flash monitor`. + +If the above command fails, check the log on the serial monitor which usually provides information on the possible cause of the issue. + +To exit the serial monitor, use `Ctrl` + `]`. + + +## Example Output + +If you see the following console output, your example should be running correctly: + +``` +I (1509) main_task: Calling app_main() +I (1509) ek79007: version: 1.0.1 +I (1549) ov2640: Detected Camera sensor PID=0x26 +I (1549) sensor_init: fmt[0].name:DVP_8bit_20Minput_RGB565_640x480_6fps +I (1549) sensor_init: fmt[1].name:DVP_8bit_20Minput_YUV422_640x480_6fps +I (1549) sensor_init: fmt[2].name:DVP_8bit_20Minput_JPEG_640x480_25fps +I (1559) sensor_init: fmt[3].name:DVP_8bit_20Minput_RGB565_240x240_25fps +I (1569) sensor_init: fmt[4].name:DVP_8bit_20Minput_YUV422_240x240_25fps +I (1569) sensor_init: fmt[5].name:DVP_8bit_20Minput_JPEG_320x240_50fps +I (1579) sensor_init: fmt[6].name:DVP_8bit_20Minput_JPEG_1280x720_12fps +I (1589) sensor_init: fmt[7].name:DVP_8bit_20Minput_JPEG_1600x1200_12fps +I (1589) sensor_init: fmt[8].name:DVP_8bit_20Minput_RAW8_800x640_15fps +I (1599) sensor_init: fmt[9].name:DVP_8bit_20Minput_RAW8_800x800_15fps +I (1609) sensor_init: fmt[10].name:DVP_8bit_20Minput_RAW8_1024x600_15fps +I (2609) sensor_init: Format in use:DVP_8bit_20Minput_RAW8_1024x600_15fps +``` + + +## Reference + +- Link to the ESP-IDF feature's API reference, for example [ESP-IDF: Camera Controller Driver](https://docs.espressif.com/projects/esp-idf/en/latest/api-reference/peripherals/camera_driver.html) +- [ESP-IDF Getting Started](https://docs.espressif.com/projects/esp-idf/en/latest/get-started/index.html#get-started) +- [Project Configuration](https://docs.espressif.com/projects/esp-idf/en/latest/api-reference/kconfig.html) (Kconfig Options) diff --git a/examples/peripherals/camera/camera_dsi/main/CMakeLists.txt b/examples/peripherals/camera/dvp_isp_dsi/main/CMakeLists.txt similarity index 77% rename from examples/peripherals/camera/camera_dsi/main/CMakeLists.txt rename to examples/peripherals/camera/dvp_isp_dsi/main/CMakeLists.txt index 545dc327d239..fa1f8c83b652 100644 --- a/examples/peripherals/camera/camera_dsi/main/CMakeLists.txt +++ b/examples/peripherals/camera/dvp_isp_dsi/main/CMakeLists.txt @@ -1,4 +1,4 @@ -idf_component_register(SRCS "camera_dsi_main.c" +idf_component_register(SRCS "dvp_isp_dsi_main.c" INCLUDE_DIRS "." REQUIRES esp_mm esp_driver_isp esp_driver_cam esp_driver_i2c dsi_init sensor_init ) diff --git a/examples/peripherals/camera/dvp_isp_dsi/main/Kconfig.projbuild b/examples/peripherals/camera/dvp_isp_dsi/main/Kconfig.projbuild new file mode 100644 index 000000000000..9366f21f792b --- /dev/null +++ b/examples/peripherals/camera/dvp_isp_dsi/main/Kconfig.projbuild @@ -0,0 +1,58 @@ +menu "Example Configuration" + config EXAMPLE_USED_LDO_CHAN_ID + int "Occupied channel ID of the LDO to power on the MIPI DSI PHY" + default 3 + help + Example used LDO channel ID, you may check datasheet to know more details. + + config EXAMPLE_USED_LDO_VOLTAGE_MV + int "Occupied LDO voltage in mV" + default 2500 + range 0 3300 + help + Example used LDO voltage, in mV + + config EXAMPLE_CAM_PORT_ISP_DVP + bool + default y + + choice EXAMPLE_CAM_HRES + bool "Set camera horizontal resolution" + default EXAMPLE_CAM_HRES_1024 if EXAMPLE_MIPI_DSI_DISP_HRES_1024 + default EXAMPLE_CAM_HRES_800 if EXAMPLE_MIPI_DSI_DISP_HRES_800 + + config EXAMPLE_CAM_HRES_1024 + depends on EXAMPLE_MIPI_DSI_DISP_HRES_1024 + bool "1024" + config EXAMPLE_CAM_HRES_800 + depends on EXAMPLE_MIPI_DSI_DISP_HRES_800 + bool "800" + endchoice + + config EXAMPLE_CAM_HRES + int + default 1024 if EXAMPLE_CAM_HRES_1024 + default 800 if EXAMPLE_CAM_HRES_800 + + choice EXAMPLE_CAM_VRES + bool "Set camera vertical resolution" + default EXAMPLE_CAM_VRES_600 if EXAMPLE_MIPI_DSI_DISP_VRES_600 + default EXAMPLE_CAM_VRES_640 if EXAMPLE_MIPI_DSI_DISP_VRES_1280 + + config EXAMPLE_CAM_VRES_600 + depends on EXAMPLE_MIPI_DSI_DISP_VRES_600 + bool "600" + config EXAMPLE_CAM_VRES_640 + depends on EXAMPLE_MIPI_DSI_DISP_VRES_1280 + bool "640" + config EXAMPLE_CAM_VRES_800 + depends on EXAMPLE_MIPI_DSI_DISP_VRES_1280 + bool "800" + endchoice + + config EXAMPLE_CAM_VRES + int + default 600 if EXAMPLE_CAM_VRES_600 + default 640 if EXAMPLE_CAM_VRES_640 + default 800 if EXAMPLE_CAM_VRES_800 +endmenu diff --git a/examples/peripherals/camera/dvp_isp_dsi/main/dvp_isp_dsi_main.c b/examples/peripherals/camera/dvp_isp_dsi/main/dvp_isp_dsi_main.c new file mode 100644 index 000000000000..144be169e686 --- /dev/null +++ b/examples/peripherals/camera/dvp_isp_dsi/main/dvp_isp_dsi_main.c @@ -0,0 +1,191 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#include +#include "sdkconfig.h" +#include "esp_attr.h" +#include "esp_log.h" +#include "freertos/FreeRTOS.h" +#include "esp_lcd_mipi_dsi.h" +#include "esp_lcd_panel_ops.h" +#include "esp_ldo_regulator.h" +#include "esp_cache.h" +#include "driver/i2c_master.h" +#include "driver/isp.h" +#include "esp_cam_ctlr_isp_dvp.h" +#include "esp_cam_ctlr.h" +#include "example_dsi_init.h" +#include "example_dsi_init_config.h" +#include "example_sensor_init.h" +#include "example_config.h" +#include "driver/ledc.h" + +static const char *TAG = "dvp_isp_dsi"; + +static bool s_camera_get_new_vb(esp_cam_ctlr_handle_t handle, esp_cam_ctlr_trans_t *trans, void *user_data); +static bool s_camera_get_finished_trans(esp_cam_ctlr_handle_t handle, esp_cam_ctlr_trans_t *trans, void *user_data); + +// Generate a 20MHz clock for DVP XCLK +static void s_ledc_generate_dvp_xclk(void) +{ + ledc_timer_config_t ledc_timer = { + .speed_mode = LEDC_LOW_SPEED_MODE, + .duty_resolution = LEDC_TIMER_1_BIT, + .timer_num = LEDC_TIMER_0, + .freq_hz = EXAMPLE_ISP_DVP_CAM_XCLK_FREQ_HZ, // Set output frequency at 20 MHz + .clk_cfg = LEDC_AUTO_CLK + }; + ESP_ERROR_CHECK(ledc_timer_config(&ledc_timer)); + + // Prepare and then apply the LEDC PWM channel configuration + ledc_channel_config_t ledc_channel = { + .speed_mode = LEDC_LOW_SPEED_MODE, + .channel = LEDC_CHANNEL_0, + .timer_sel = LEDC_TIMER_0, + .intr_type = LEDC_INTR_DISABLE, + .gpio_num = EXAMPLE_ISP_DVP_CAM_XCLK_IO, + .duty = 1, // Set duty to 50% + .hpoint = 0 + }; + ESP_ERROR_CHECK(ledc_channel_config(&ledc_channel)); +} + +void app_main(void) +{ + esp_err_t ret = ESP_FAIL; + esp_lcd_dsi_bus_handle_t mipi_dsi_bus = NULL; + esp_lcd_panel_io_handle_t mipi_dbi_io = NULL; + esp_lcd_panel_handle_t mipi_dpi_panel = NULL; + void *frame_buffer = NULL; + size_t frame_buffer_size = 0; + + //mipi ldo + esp_ldo_channel_handle_t ldo_mipi_phy = NULL; + esp_ldo_channel_config_t ldo_mipi_phy_config = { + .chan_id = CONFIG_EXAMPLE_USED_LDO_CHAN_ID, + .voltage_mv = CONFIG_EXAMPLE_USED_LDO_VOLTAGE_MV, + }; + ESP_ERROR_CHECK(esp_ldo_acquire_channel(&ldo_mipi_phy_config, &ldo_mipi_phy)); + + /** + * @background + * Sensor use RAW8 + * ISP convert to RGB565 + */ + //---------------DSI Init------------------// + example_dsi_resource_alloc(&mipi_dsi_bus, &mipi_dbi_io, &mipi_dpi_panel, &frame_buffer); + + //---------------Necessary variable config------------------// + frame_buffer_size = CONFIG_EXAMPLE_CAM_HRES * CONFIG_EXAMPLE_MIPI_DSI_DISP_VRES * EXAMPLE_RGB565_BITS_PER_PIXEL / 8; + + ESP_LOGD(TAG, "CONFIG_EXAMPLE_CAM_HRES: %d, CONFIG_EXAMPLE_MIPI_DSI_DISP_VRES: %d, bits per pixel: %d", CONFIG_EXAMPLE_CAM_HRES, CONFIG_EXAMPLE_MIPI_DSI_DISP_VRES, EXAMPLE_RGB565_BITS_PER_PIXEL); + ESP_LOGD(TAG, "frame_buffer_size: %zu", frame_buffer_size); + ESP_LOGD(TAG, "frame_buffer: %p", frame_buffer); + + esp_cam_ctlr_trans_t cam_trans = { + .buffer = frame_buffer, + .buflen = frame_buffer_size, + }; + + //--------Camera Sensor and SCCB Init-----------// + s_ledc_generate_dvp_xclk(); + + example_sensor_config_t cam_sensor_config = { + .i2c_port_num = I2C_NUM_0, + .i2c_sda_io_num = EXAMPLE_ISP_DVP_CAM_SCCB_SDA_IO, + .i2c_scl_io_num = EXAMPLE_ISP_DVP_CAM_SCCB_SCL_IO, + .port = ESP_CAM_SENSOR_DVP, + .format_name = EXAMPLE_CAM_FORMAT, + }; + i2c_master_bus_handle_t i2c_bus_handle = NULL; + example_sensor_init(&cam_sensor_config, &i2c_bus_handle); + + //---------------ISP Init------------------// + isp_proc_handle_t isp_proc = NULL; + esp_isp_processor_cfg_t isp_config = { + .clk_hz = 80 * 1000 * 1000, + .input_data_source = ISP_INPUT_DATA_SOURCE_DVP, + .input_data_color_type = ISP_COLOR_RAW8, + .output_data_color_type = ISP_COLOR_RGB565, + .has_line_start_packet = false, + .has_line_end_packet = false, + .h_res = CONFIG_EXAMPLE_CAM_HRES, + .v_res = CONFIG_EXAMPLE_CAM_VRES, + }; + ESP_ERROR_CHECK(esp_isp_new_processor(&isp_config, &isp_proc)); + ESP_ERROR_CHECK(esp_isp_enable(isp_proc)); + + //----------CAM Controller Init------------// + esp_cam_ctlr_handle_t cam_handle = NULL; + esp_cam_ctlr_isp_dvp_cfg_t dvp_config = { + .data_width = EXAMPLE_ISP_DVP_CAM_DATA_WIDTH, + .data_io = { + EXAMPLE_ISP_DVP_CAM_D0_IO, + EXAMPLE_ISP_DVP_CAM_D1_IO, + EXAMPLE_ISP_DVP_CAM_D2_IO, + EXAMPLE_ISP_DVP_CAM_D3_IO, + EXAMPLE_ISP_DVP_CAM_D4_IO, + EXAMPLE_ISP_DVP_CAM_D5_IO, + EXAMPLE_ISP_DVP_CAM_D6_IO, + EXAMPLE_ISP_DVP_CAM_D7_IO, + }, + .pclk_io = EXAMPLE_ISP_DVP_CAM_PCLK_IO, + .hsync_io = EXAMPLE_ISP_DVP_CAM_HSYNC_IO, + .vsync_io = EXAMPLE_ISP_DVP_CAM_VSYNC_IO, + .de_io = EXAMPLE_ISP_DVP_CAM_DE_IO, + .io_flags.vsync_invert = 1, + .queue_items = 10, + }; + ret = esp_cam_new_isp_dvp_ctlr(isp_proc, &dvp_config, &cam_handle); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "isp dvp init fail[%d]", ret); + return; + } + + esp_cam_ctlr_evt_cbs_t cbs = { + .on_get_new_trans = s_camera_get_new_vb, + .on_trans_finished = s_camera_get_finished_trans, + }; + if (esp_cam_ctlr_register_event_callbacks(cam_handle, &cbs, &cam_trans) != ESP_OK) { + ESP_LOGE(TAG, "ops register fail"); + return; + } + + ESP_ERROR_CHECK(esp_cam_ctlr_enable(cam_handle)); + + //---------------DPI Reset------------------// + example_dpi_panel_reset(mipi_dpi_panel); + + //init to all white + memset(frame_buffer, 0xFF, frame_buffer_size); + esp_cache_msync((void *)frame_buffer, frame_buffer_size, ESP_CACHE_MSYNC_FLAG_DIR_C2M); + + example_dpi_panel_init(mipi_dpi_panel); + + if (esp_cam_ctlr_start(cam_handle) != ESP_OK) { + ESP_LOGE(TAG, "Driver start fail"); + return; + } + + while (1) { + ESP_ERROR_CHECK(esp_cam_ctlr_receive(cam_handle, &cam_trans, ESP_CAM_CTLR_MAX_DELAY)); + } +} + +static bool s_camera_get_new_vb(esp_cam_ctlr_handle_t handle, esp_cam_ctlr_trans_t *trans, void *user_data) +{ + esp_cam_ctlr_trans_t cam_trans = *(esp_cam_ctlr_trans_t *)user_data; + trans->buffer = cam_trans.buffer; + trans->buflen = cam_trans.buflen; + + return false; +} + +static bool s_camera_get_finished_trans(esp_cam_ctlr_handle_t handle, esp_cam_ctlr_trans_t *trans, void *user_data) +{ + return false; +} diff --git a/examples/peripherals/camera/dvp_isp_dsi/main/example_config.h b/examples/peripherals/camera/dvp_isp_dsi/main/example_config.h new file mode 100644 index 000000000000..0bf8bd5941ce --- /dev/null +++ b/examples/peripherals/camera/dvp_isp_dsi/main/example_config.h @@ -0,0 +1,55 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "sdkconfig.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define EXAMPLE_RGB565_BITS_PER_PIXEL 16 + +#define EXAMPLE_ISP_DVP_CAM_SCCB_SCL_IO (33) +#define EXAMPLE_ISP_DVP_CAM_SCCB_SDA_IO (32) + +#define EXAMPLE_ISP_DVP_CAM_XCLK_FREQ_HZ (20000000) + +#define EXAMPLE_ISP_DVP_CAM_DATA_WIDTH (8) +#define EXAMPLE_ISP_DVP_CAM_D0_IO (8) +#define EXAMPLE_ISP_DVP_CAM_D1_IO (9) +#define EXAMPLE_ISP_DVP_CAM_D2_IO (10) +#define EXAMPLE_ISP_DVP_CAM_D3_IO (4) +#define EXAMPLE_ISP_DVP_CAM_D4_IO (5) +#define EXAMPLE_ISP_DVP_CAM_D5_IO (45) +#define EXAMPLE_ISP_DVP_CAM_D6_IO (46) +#define EXAMPLE_ISP_DVP_CAM_D7_IO (47) +#define EXAMPLE_ISP_DVP_CAM_XCLK_IO (20) +#define EXAMPLE_ISP_DVP_CAM_PCLK_IO (21) +#define EXAMPLE_ISP_DVP_CAM_DE_IO (2) +#define EXAMPLE_ISP_DVP_CAM_VSYNC_IO (23) +#define EXAMPLE_ISP_DVP_CAM_HSYNC_IO (-1) + +#if CONFIG_EXAMPLE_CAM_HRES_800 + +#if CONFIG_EXAMPLE_CAM_VRES_640 +#define EXAMPLE_CAM_FORMAT "DVP_8bit_20Minput_RAW8_800x640_15fps" +#elif CONFIG_EXAMPLE_CAM_VRES_800 +#define EXAMPLE_CAM_FORMAT "DVP_8bit_20Minput_RAW8_800x800_15fps" +#endif + +#elif CONFIG_EXAMPLE_CAM_HRES_1024 + +#if CONFIG_EXAMPLE_CAM_VRES_600 +#define EXAMPLE_CAM_FORMAT "DVP_8bit_20Minput_RAW8_1024x600_15fps" +#endif + +#endif + +#ifdef __cplusplus +} +#endif diff --git a/examples/peripherals/camera/dvp_isp_dsi/main/idf_component.yml b/examples/peripherals/camera/dvp_isp_dsi/main/idf_component.yml new file mode 100644 index 000000000000..5aeb9ac3f513 --- /dev/null +++ b/examples/peripherals/camera/dvp_isp_dsi/main/idf_component.yml @@ -0,0 +1,7 @@ +dependencies: + idf: + version: ">=5.3.0" + dsi_init: + path: ${IDF_PATH}/examples/peripherals/camera/common_components/dsi_init + sensor_init: + path: ${IDF_PATH}/examples/peripherals/camera/common_components/sensor_init diff --git a/examples/peripherals/camera/camera_dsi/pytest_camera_dsi.py b/examples/peripherals/camera/dvp_isp_dsi/pytest_dvp_isp_dsi.py similarity index 85% rename from examples/peripherals/camera/camera_dsi/pytest_camera_dsi.py rename to examples/peripherals/camera/dvp_isp_dsi/pytest_dvp_isp_dsi.py index 9c234e62174c..7eb81af00186 100644 --- a/examples/peripherals/camera/camera_dsi/pytest_camera_dsi.py +++ b/examples/peripherals/camera/dvp_isp_dsi/pytest_dvp_isp_dsi.py @@ -6,5 +6,5 @@ @pytest.mark.esp32p4 @pytest.mark.generic -def test_camera_dsi(dut: Dut) -> None: +def test_dvp_isp_dsi(dut: Dut) -> None: dut.expect_exact('Calling app_main()') diff --git a/examples/peripherals/camera/dvp_isp_dsi/sdkconfig.defaults b/examples/peripherals/camera/dvp_isp_dsi/sdkconfig.defaults new file mode 100644 index 000000000000..3e05d2e5e64d --- /dev/null +++ b/examples/peripherals/camera/dvp_isp_dsi/sdkconfig.defaults @@ -0,0 +1,4 @@ +CONFIG_SPIRAM=y +CONFIG_IDF_EXPERIMENTAL_FEATURES=y +CONFIG_SPIRAM_SPEED_200M=y +CONFIG_CAMERA_OV2640=y diff --git a/examples/peripherals/camera/mipi_isp_dsi/CMakeLists.txt b/examples/peripherals/camera/mipi_isp_dsi/CMakeLists.txt new file mode 100644 index 000000000000..c63528b6f9a3 --- /dev/null +++ b/examples/peripherals/camera/mipi_isp_dsi/CMakeLists.txt @@ -0,0 +1,8 @@ +# The following lines of boilerplate have to be in your project's CMakeLists +# in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.16) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +# "Trim" the build. Include the minimal set of components, main, and anything it depends on. +idf_build_set_property(MINIMAL_BUILD ON) +project(mipi_isp_dsi) diff --git a/examples/peripherals/camera/camera_dsi/README.md b/examples/peripherals/camera/mipi_isp_dsi/README.md similarity index 90% rename from examples/peripherals/camera/camera_dsi/README.md rename to examples/peripherals/camera/mipi_isp_dsi/README.md index 60a3d9d2b455..d8394550fb2a 100644 --- a/examples/peripherals/camera/camera_dsi/README.md +++ b/examples/peripherals/camera/mipi_isp_dsi/README.md @@ -2,11 +2,11 @@ | ----------------- | -------- | -# Camera display via DSI example +# MIPI Camera display via DSI example ## Overview -This example demonstrates how to use the esp_driver_cam component to capture camera sensor signals and display it via DSI interface. This example will auto-detect camera sensors via [ESP camera sensor driver](https://components.espressif.com/components/espressif/esp_cam_sensor/versions/0.5.3) and capture camera sensor signals via CSI interface and display it via DSI interface. +This example demonstrates how to use the esp_driver_cam component to capture MIPI-CSI camera sensor signals and display it via DSI interface. This example will auto-detect camera sensors via [ESP camera sensor driver](https://components.espressif.com/components/espressif/esp_cam_sensor/versions/0.5.3) and capture camera sensor signals via CSI interface and display it via DSI interface. ## Usage @@ -15,7 +15,7 @@ The subsections below give only absolutely necessary information. For full steps ### Hardware Required -- OV5647 or SC2336 camera sensor, or other camera sensors +- OV5647 or SC2336 camera sensor, or other camera sensors with MIPI-CSI port - EK79007 or ILI9881C LCD screen - ESP32P4 devkit @@ -107,6 +107,10 @@ Set CONFIG_CAMERA_OV5647 to y Set CONFIG_CAMERA_SC2336 to y ``` +Remember to select the LCD screen model and set corresponding correct horizontal/vertical resolution in ``menuconfig`` > ``Example DSI Configuration``. + +Available options for the camera sensor output horizontal/vertical resolution can be seen in ``menuconfig`` > ``Example Configuration``. Note that the horizontal resolution for the camera should be the same as the LCD screen horizontal resolution. + ### Build and Flash @@ -133,10 +137,10 @@ I (1095) ili9881c: ID1: 0x98, ID2: 0x81, ID3: 0x5c I (1125) gpio: GPIO[31]| InputEn: 1| OutputEn: 1| OpenDrain: 1| Pullup: 1| Pulldown: 0| Intr:0 I (1125) gpio: GPIO[34]| InputEn: 1| OutputEn: 1| OpenDrain: 1| Pullup: 1| Pulldown: 0| Intr:0 I (1295) ov5647: Detected Camera sensor PID=0x5647 with index 0 -I (1305) cam_dsi: fmt[0].name:MIPI_2lane_24Minput_RAW8_800x1280_50fps -I (1305) cam_dsi: fmt[1].name:MIPI_2lane_24Minput_RAW8_800x640_50fps -I (1315) cam_dsi: fmt[2].name:MIPI_2lane_24Minput_RAW8_800x800_50fps -I (1355) cam_dsi: Format in use:MIPI_2lane_24Minput_RAW8_800x640_50fps +I (1305) sensor_init: fmt[0].name:MIPI_2lane_24Minput_RAW8_800x1280_50fps +I (1305) sensor_init: fmt[1].name:MIPI_2lane_24Minput_RAW8_800x640_50fps +I (1315) sensor_init: fmt[2].name:MIPI_2lane_24Minput_RAW8_800x800_50fps +I (1355) sensor_init: Format in use:MIPI_2lane_24Minput_RAW8_800x640_50fps ``` diff --git a/examples/peripherals/camera/mipi_isp_dsi/main/CMakeLists.txt b/examples/peripherals/camera/mipi_isp_dsi/main/CMakeLists.txt new file mode 100644 index 000000000000..f0896c32b3a0 --- /dev/null +++ b/examples/peripherals/camera/mipi_isp_dsi/main/CMakeLists.txt @@ -0,0 +1,4 @@ +idf_component_register(SRCS "mipi_isp_dsi_main.c" + INCLUDE_DIRS "." + REQUIRES esp_mm esp_driver_isp esp_driver_cam esp_driver_i2c dsi_init sensor_init + ) diff --git a/examples/peripherals/camera/camera_dsi/main/Kconfig.projbuild b/examples/peripherals/camera/mipi_isp_dsi/main/Kconfig.projbuild similarity index 88% rename from examples/peripherals/camera/camera_dsi/main/Kconfig.projbuild rename to examples/peripherals/camera/mipi_isp_dsi/main/Kconfig.projbuild index 98a42e128a77..d2d601b230f4 100644 --- a/examples/peripherals/camera/camera_dsi/main/Kconfig.projbuild +++ b/examples/peripherals/camera/mipi_isp_dsi/main/Kconfig.projbuild @@ -12,6 +12,10 @@ menu "Example Configuration" help Example used LDO voltage, in mV + config EXAMPLE_CAM_PORT_ISP_MIPI + bool + default y + choice EXAMPLE_MIPI_CSI_DISP_HRES bool "Set MIPI CSI horizontal resolution" default EXAMPLE_MIPI_CSI_HRES_1024 @@ -35,6 +39,8 @@ menu "Example Configuration" bool "600" config EXAMPLE_MIPI_CSI_VRES_640 bool "640" + config EXAMPLE_MIPI_CSI_VRES_800 + bool "800" config EXAMPLE_MIPI_CSI_VRES_1280 bool "1280" endchoice @@ -43,5 +49,6 @@ menu "Example Configuration" int default 600 if EXAMPLE_MIPI_CSI_VRES_600 default 640 if EXAMPLE_MIPI_CSI_VRES_640 + default 800 if EXAMPLE_MIPI_CSI_VRES_800 default 1280 if EXAMPLE_MIPI_CSI_VRES_1280 endmenu diff --git a/examples/peripherals/camera/camera_dsi/components/sensor_init/include/example_sensor_init_config.h b/examples/peripherals/camera/mipi_isp_dsi/main/example_config.h similarity index 52% rename from examples/peripherals/camera/camera_dsi/components/sensor_init/include/example_sensor_init_config.h rename to examples/peripherals/camera/mipi_isp_dsi/main/example_config.h index 889122dbe1a6..570ebdc86692 100644 --- a/examples/peripherals/camera/camera_dsi/components/sensor_init/include/example_sensor_init_config.h +++ b/examples/peripherals/camera/mipi_isp_dsi/main/example_config.h @@ -12,22 +12,31 @@ extern "C" { #endif -#define EXAMPLE_CAM_SCCB_FREQ (100000) -#define EXAMPLE_CAM_SCCB_SCL_IO (8) -#define EXAMPLE_CAM_SCCB_SDA_IO (7) +#define EXAMPLE_RGB565_BITS_PER_PIXEL 16 +#define EXAMPLE_MIPI_IDI_CLOCK_RATE (50000000) +#define EXAMPLE_MIPI_CSI_LANE_BITRATE_MBPS 200 //line_rate = pclk * 4 + +#define EXAMPLE_MIPI_CSI_CAM_SCCB_SCL_IO (8) +#define EXAMPLE_MIPI_CSI_CAM_SCCB_SDA_IO (7) #if CONFIG_EXAMPLE_MIPI_CSI_HRES_800 + #if CONFIG_EXAMPLE_MIPI_CSI_VRES_640 #define EXAMPLE_CAM_FORMAT "MIPI_2lane_24Minput_RAW8_800x640_50fps" -#else +#elif CONFIG_EXAMPLE_MIPI_CSI_VRES_800 +#define EXAMPLE_CAM_FORMAT "MIPI_2lane_24Minput_RAW8_800x800_50fps" +#elif CONFIG_EXAMPLE_MIPI_CSI_VRES_1280 #define EXAMPLE_CAM_FORMAT "MIPI_2lane_24Minput_RAW8_800x1280_50fps" #endif -#endif -#if CONFIG_EXAMPLE_MIPI_CSI_HRES_1024 +#elif CONFIG_EXAMPLE_MIPI_CSI_HRES_1024 + +#if CONFIG_EXAMPLE_MIPI_CSI_VRES_600 #define EXAMPLE_CAM_FORMAT "MIPI_2lane_24Minput_RAW8_1024x600_30fps" #endif +#endif + #ifdef __cplusplus } #endif diff --git a/examples/peripherals/camera/mipi_isp_dsi/main/idf_component.yml b/examples/peripherals/camera/mipi_isp_dsi/main/idf_component.yml new file mode 100644 index 000000000000..5aeb9ac3f513 --- /dev/null +++ b/examples/peripherals/camera/mipi_isp_dsi/main/idf_component.yml @@ -0,0 +1,7 @@ +dependencies: + idf: + version: ">=5.3.0" + dsi_init: + path: ${IDF_PATH}/examples/peripherals/camera/common_components/dsi_init + sensor_init: + path: ${IDF_PATH}/examples/peripherals/camera/common_components/sensor_init diff --git a/examples/peripherals/camera/camera_dsi/main/camera_dsi_main.c b/examples/peripherals/camera/mipi_isp_dsi/main/mipi_isp_dsi_main.c similarity index 91% rename from examples/peripherals/camera/camera_dsi/main/camera_dsi_main.c rename to examples/peripherals/camera/mipi_isp_dsi/main/mipi_isp_dsi_main.c index ae8f0e244361..34ef7368e0a1 100644 --- a/examples/peripherals/camera/camera_dsi/main/camera_dsi_main.c +++ b/examples/peripherals/camera/mipi_isp_dsi/main/mipi_isp_dsi_main.c @@ -23,7 +23,7 @@ #include "example_sensor_init.h" #include "example_config.h" -static const char *TAG = "cam_dsi"; +static const char *TAG = "mipi_isp_dsi"; static bool s_camera_get_new_vb(esp_cam_ctlr_handle_t handle, esp_cam_ctlr_trans_t *trans, void *user_data); static bool s_camera_get_finished_trans(esp_cam_ctlr_handle_t handle, esp_cam_ctlr_trans_t *trans, void *user_data); @@ -56,7 +56,7 @@ void app_main(void) //---------------Necessary variable config------------------// frame_buffer_size = CONFIG_EXAMPLE_MIPI_CSI_DISP_HRES * CONFIG_EXAMPLE_MIPI_DSI_DISP_VRES * EXAMPLE_RGB565_BITS_PER_PIXEL / 8; - ESP_LOGD(TAG, "CONFIG_EXAMPLE_MIPI_CSI_DISP_HRES: %d, CONFIG_EXAMPLE_MIPI_DSI_DISP_VRES: %d, bits per pixel: %d", CONFIG_EXAMPLE_MIPI_CSI_DISP_HRES, CONFIG_EXAMPLE_MIPI_DSI_DISP_VRES, 8); + ESP_LOGD(TAG, "CONFIG_EXAMPLE_MIPI_CSI_DISP_HRES: %d, CONFIG_EXAMPLE_MIPI_DSI_DISP_VRES: %d, bits per pixel: %d", CONFIG_EXAMPLE_MIPI_CSI_DISP_HRES, CONFIG_EXAMPLE_MIPI_DSI_DISP_VRES, EXAMPLE_RGB565_BITS_PER_PIXEL); ESP_LOGD(TAG, "frame_buffer_size: %zu", frame_buffer_size); ESP_LOGD(TAG, "frame_buffer: %p", frame_buffer); @@ -67,7 +67,14 @@ void app_main(void) //--------Camera Sensor and SCCB Init-----------// i2c_master_bus_handle_t i2c_bus_handle = NULL; - example_sensor_init(I2C_NUM_0, &i2c_bus_handle); + example_sensor_config_t cam_sensor_config = { + .i2c_port_num = I2C_NUM_0, + .i2c_sda_io_num = EXAMPLE_MIPI_CSI_CAM_SCCB_SDA_IO, + .i2c_scl_io_num = EXAMPLE_MIPI_CSI_CAM_SCCB_SCL_IO, + .port = ESP_CAM_SENSOR_MIPI_CSI, + .format_name = EXAMPLE_CAM_FORMAT, + }; + example_sensor_init(&cam_sensor_config, &i2c_bus_handle); //---------------CSI Init------------------// esp_cam_ctlr_csi_config_t csi_config = { diff --git a/examples/peripherals/camera/mipi_isp_dsi/pytest_mipi_isp_dsi.py b/examples/peripherals/camera/mipi_isp_dsi/pytest_mipi_isp_dsi.py new file mode 100644 index 000000000000..44da4af5ef51 --- /dev/null +++ b/examples/peripherals/camera/mipi_isp_dsi/pytest_mipi_isp_dsi.py @@ -0,0 +1,10 @@ +# SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: CC0-1.0 +import pytest +from pytest_embedded import Dut + + +@pytest.mark.esp32p4 +@pytest.mark.generic +def test_mipi_isp_dsi(dut: Dut) -> None: + dut.expect_exact('Calling app_main()') diff --git a/examples/peripherals/camera/camera_dsi/sdkconfig.defaults b/examples/peripherals/camera/mipi_isp_dsi/sdkconfig.defaults similarity index 100% rename from examples/peripherals/camera/camera_dsi/sdkconfig.defaults rename to examples/peripherals/camera/mipi_isp_dsi/sdkconfig.defaults diff --git a/examples/peripherals/isp/multi_pipelines/README.md b/examples/peripherals/isp/multi_pipelines/README.md index bfd80fbec16d..840176ea5c11 100644 --- a/examples/peripherals/isp/multi_pipelines/README.md +++ b/examples/peripherals/isp/multi_pipelines/README.md @@ -116,6 +116,10 @@ Set CONFIG_CAMERA_OV5647 to y Set CONFIG_CAMERA_SC2336 to y ``` +Remember to select the LCD screen model and set corresponding correct horizontal/vertical resolution in ``menuconfig`` > ``Example DSI Configuration``. + +Available options for the camera sensor output horizontal/vertical resolution can be seen in ``menuconfig`` > ``Example Configuration``. Note that the horizontal resolution for the camera should be the same as the LCD screen horizontal resolution. + ### Build and Flash diff --git a/examples/peripherals/isp/multi_pipelines/main/example_config.h b/examples/peripherals/isp/multi_pipelines/main/example_config.h index 00775fd4c4fa..9c5482fe3926 100644 --- a/examples/peripherals/isp/multi_pipelines/main/example_config.h +++ b/examples/peripherals/isp/multi_pipelines/main/example_config.h @@ -14,6 +14,26 @@ extern "C" { #define EXAMPLE_MIPI_SCCB_FREQ (100000) #define EXAMPLE_MIPI_CSI_LANE_BITRATE_MBPS 200 //line_rate = pclk * 4 +#define EXAMPLE_MIPI_CSI_CAM_SCCB_SCL_IO (8) +#define EXAMPLE_MIPI_CSI_CAM_SCCB_SDA_IO (7) + +#if CONFIG_EXAMPLE_MIPI_CSI_HRES_800 + +#if CONFIG_EXAMPLE_MIPI_CSI_VRES_640 +#define EXAMPLE_CAM_FORMAT "MIPI_2lane_24Minput_RAW8_800x640_50fps" +#elif CONFIG_EXAMPLE_MIPI_CSI_VRES_800 +#define EXAMPLE_CAM_FORMAT "MIPI_2lane_24Minput_RAW8_800x800_50fps" +#elif CONFIG_EXAMPLE_MIPI_CSI_VRES_1280 +#define EXAMPLE_CAM_FORMAT "MIPI_2lane_24Minput_RAW8_800x1280_50fps" +#endif + +#elif CONFIG_EXAMPLE_MIPI_CSI_HRES_1024 + +#if CONFIG_EXAMPLE_MIPI_CSI_VRES_600 +#define EXAMPLE_CAM_FORMAT "MIPI_2lane_24Minput_RAW8_1024x600_30fps" +#endif + +#endif #define EXAMPLE_DW9714_DEV_ADDR 0xC #ifdef __cplusplus } diff --git a/examples/peripherals/isp/multi_pipelines/main/idf_component.yml b/examples/peripherals/isp/multi_pipelines/main/idf_component.yml index 3ba04971a7e5..3081b5cd829e 100644 --- a/examples/peripherals/isp/multi_pipelines/main/idf_component.yml +++ b/examples/peripherals/isp/multi_pipelines/main/idf_component.yml @@ -1,10 +1,10 @@ dependencies: - espressif/esp_cam_sensor: "^0.5.*" + espressif/esp_cam_sensor: ">=0.5.*" idf: version: ">=5.3.0" isp_af_schemes: path: ${IDF_PATH}/examples/peripherals/isp/multi_pipelines/components/isp_af_schemes dsi_init: - path: ${IDF_PATH}/examples/peripherals/camera/camera_dsi/components/dsi_init + path: ${IDF_PATH}/examples/peripherals/camera/common_components/dsi_init sensor_init: - path: ${IDF_PATH}/examples/peripherals/camera/camera_dsi/components/sensor_init + path: ${IDF_PATH}/examples/peripherals/camera/common_components/sensor_init diff --git a/examples/peripherals/isp/multi_pipelines/main/isp_dsi_main.c b/examples/peripherals/isp/multi_pipelines/main/isp_dsi_main.c index 3f2db636db8e..67d1a3d857c4 100644 --- a/examples/peripherals/isp/multi_pipelines/main/isp_dsi_main.c +++ b/examples/peripherals/isp/multi_pipelines/main/isp_dsi_main.c @@ -224,7 +224,14 @@ void app_main(void) //--------Camera Sensor and SCCB Init-----------// i2c_master_bus_handle_t i2c_bus_handle = NULL; - example_sensor_init(I2C_NUM_0, &i2c_bus_handle); + example_sensor_config_t cam_sensor_config = { + .i2c_port_num = I2C_NUM_0, + .i2c_sda_io_num = EXAMPLE_MIPI_CSI_CAM_SCCB_SDA_IO, + .i2c_scl_io_num = EXAMPLE_MIPI_CSI_CAM_SCCB_SCL_IO, + .port = ESP_CAM_SENSOR_MIPI_CSI, + .format_name = EXAMPLE_CAM_FORMAT, + }; + example_sensor_init(&cam_sensor_config, &i2c_bus_handle); //---------------VCM SCCB Init------------------// esp_sccb_io_handle_t dw9714_io_handle = NULL; diff --git a/examples/peripherals/ppa/ppa_dsi/main/idf_component.yml b/examples/peripherals/ppa/ppa_dsi/main/idf_component.yml index 57233aaf3266..4345977f95ff 100644 --- a/examples/peripherals/ppa/ppa_dsi/main/idf_component.yml +++ b/examples/peripherals/ppa/ppa_dsi/main/idf_component.yml @@ -2,5 +2,5 @@ dependencies: idf: version: ">=5.3.0" dsi_init: - path: ${IDF_PATH}/examples/peripherals/camera/camera_dsi/components/dsi_init + path: ${IDF_PATH}/examples/peripherals/camera/common_components/dsi_init esp_jpeg: ">=1.0.2" diff --git a/examples/protocols/.build-test-rules.yml b/examples/protocols/.build-test-rules.yml index 35295523a4b3..1efc6a749595 100644 --- a/examples/protocols/.build-test-rules.yml +++ b/examples/protocols/.build-test-rules.yml @@ -10,6 +10,13 @@ - mbedtls - protocol_examples_common +.mqtt_dependencies: &mqtt_dependencies + <<: *default_dependencies + depends_filepatterns: + - components/mqtt/**/* + depends_components+: + - mqtt + examples/protocols/esp_http_client: <<: *default_dependencies enable: @@ -134,51 +141,55 @@ examples/protocols/modbus: - examples/common_components/protocol_examples_common/**/* - examples/protocols/modbus/mb_example_common/**/* - -examples/protocols/mqtt: - <<: *default_dependencies - depends_filepatterns: - - components/mqtt/**/* - examples/protocols/mqtt/custom_outbox: - <<: *default_dependencies + <<: *mqtt_dependencies examples/protocols/mqtt/ssl: - <<: *default_dependencies + <<: *mqtt_dependencies disable_test: - if: IDF_TARGET != "esp32" reason: only test on esp32 examples/protocols/mqtt/ssl_ds: - <<: *default_dependencies + <<: *mqtt_dependencies disable: - if: SOC_DIG_SIGN_SUPPORTED != 1 temporary: false reason: DS not present + depends_filepatterns+: + - examples/protocols/mqtt/ssl_ds/**/* examples/protocols/mqtt/tcp: - <<: *default_dependencies + <<: *mqtt_dependencies disable_test: - if: IDF_TARGET != "esp32" reason: only test on esp32 + depends_filepatterns+: + - examples/protocols/mqtt/tcp/**/* examples/protocols/mqtt/ws: - <<: *default_dependencies + <<: *mqtt_dependencies disable_test: - if: IDF_TARGET != "esp32" reason: only test on esp32 + depends_filepatterns+: + - examples/protocols/mqtt/ws/**/* examples/protocols/mqtt/wss: - <<: *default_dependencies + <<: *mqtt_dependencies disable_test: - if: IDF_TARGET != "esp32" reason: only test on esp32 + depends_filepatterns+: + - examples/protocols/mqtt/wss/**/* examples/protocols/mqtt5: - <<: *default_dependencies + <<: *mqtt_dependencies disable_test: - if: IDF_TARGET != "esp32" reason: only test on esp32 + depends_filepatterns+: + - examples/protocols/mqtt5/**/* examples/protocols/smtp_client: <<: *default_dependencies diff --git a/examples/protocols/http_server/simple/README.md b/examples/protocols/http_server/simple/README.md index 9a82716de646..a744aac35f1b 100644 --- a/examples/protocols/http_server/simple/README.md +++ b/examples/protocols/http_server/simple/README.md @@ -6,6 +6,15 @@ The Example consists of HTTPD server demo with demonstration of URI handling : 1. URI \hello for GET command returns "Hello World!" message 2. URI \echo for POST command echoes back the POSTed message + 3. URI \sse for GET command sends a message to client every second + +## User Callback + +The example includes a simple user callback that can be used to get the SSL context (connection information) when the server is being initialized. To enable the user callback, set `CONFIG_EXAMPLE_ENABLE_HTTPS_USER_CALLBACK` to `y` in the project configuration menu. + +## Server-Sent Events (SSE) + +The example also includes a simple SSE handler (having endpoint \sse), which sends a message to the client every second. To enable SSE, set `CONFIG_EXAMPLE_ENABLE_SSE_HANDLER` to `y` in the project configuration menu. ## How to use example diff --git a/examples/protocols/http_server/simple/main/Kconfig.projbuild b/examples/protocols/http_server/simple/main/Kconfig.projbuild index 2611ab44ac41..87f7a449bcc7 100644 --- a/examples/protocols/http_server/simple/main/Kconfig.projbuild +++ b/examples/protocols/http_server/simple/main/Kconfig.projbuild @@ -26,4 +26,11 @@ menu "Example Configuration" help The client's password which used for basic authenticate. + config EXAMPLE_ENABLE_SSE_HANDLER + bool "Enable Server-Sent Events (SSE) handler" + default n + help + Enable this option to use Server-Sent Events (SSE) functionality. + This will allow the server to push real-time updates to the client over an HTTP connection. + endmenu diff --git a/examples/protocols/http_server/simple/main/main.c b/examples/protocols/http_server/simple/main/main.c index e13810a4e122..1a237a2c4616 100644 --- a/examples/protocols/http_server/simple/main/main.c +++ b/examples/protocols/http_server/simple/main/main.c @@ -22,7 +22,8 @@ #include "esp_netif.h" #include "esp_tls.h" #include "esp_check.h" - +#include +#include #if !CONFIG_IDF_TARGET_LINUX #include #include @@ -392,6 +393,40 @@ static const httpd_uri_t ctrl = { .user_ctx = NULL }; +#if CONFIG_EXAMPLE_ENABLE_SSE_HANDLER +/* An HTTP GET handler for SSE */ +static esp_err_t sse_handler(httpd_req_t *req) +{ + httpd_resp_set_type(req, "text/event-stream"); + httpd_resp_set_hdr(req, "Cache-Control", "no-cache"); + httpd_resp_set_hdr(req, "Connection", "keep-alive"); + + char sse_data[64]; + while (1) { + struct timeval tv; + gettimeofday(&tv, NULL); // Get the current time + int64_t time_since_boot = tv.tv_sec; // Time since boot in seconds + esp_err_t err; + int len = snprintf(sse_data, sizeof(sse_data), "data: Time since boot: %lld seconds\n\n", time_since_boot); + if ((err = httpd_resp_send_chunk(req, sse_data, len)) != ESP_OK) { + ESP_LOGE(TAG, "Failed to send sse data (returned %02X)", err); + break; + } + vTaskDelay(pdMS_TO_TICKS(1000)); // Send data every second + } + + httpd_resp_send_chunk(req, NULL, 0); // End response + return ESP_OK; +} + +static const httpd_uri_t sse = { + .uri = "/sse", + .method = HTTP_GET, + .handler = sse_handler, + .user_ctx = NULL +}; +#endif // CONFIG_EXAMPLE_ENABLE_SSE_HANDLER + static httpd_handle_t start_webserver(void) { httpd_handle_t server = NULL; @@ -414,9 +449,12 @@ static httpd_handle_t start_webserver(void) httpd_register_uri_handler(server, &echo); httpd_register_uri_handler(server, &ctrl); httpd_register_uri_handler(server, &any); - #if CONFIG_EXAMPLE_BASIC_AUTH +#if CONFIG_EXAMPLE_ENABLE_SSE_HANDLER + httpd_register_uri_handler(server, &sse); // Register SSE handler +#endif +#if CONFIG_EXAMPLE_BASIC_AUTH httpd_register_basic_auth(server); - #endif +#endif return server; } diff --git a/examples/protocols/http_server/simple/pytest_http_server_simple.py b/examples/protocols/http_server/simple/pytest_http_server_simple.py index 0827bdb60edb..1894b755d830 100644 --- a/examples/protocols/http_server/simple/pytest_http_server_simple.py +++ b/examples/protocols/http_server/simple/pytest_http_server_simple.py @@ -1,6 +1,6 @@ #!/usr/bin/env python # -# SPDX-FileCopyrightText: 2018-2022 Espressif Systems (Shanghai) CO LTD +# SPDX-FileCopyrightText: 2018-2024 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Apache-2.0 import logging import os @@ -14,6 +14,7 @@ import pytest try: + import http.client from idf_http_server_test import client except ModuleNotFoundError: sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', '..', '..', 'tools', 'ci', 'python_packages')) @@ -171,3 +172,58 @@ def test_examples_protocol_http_server_lru_purge_enable(dut: Dut) -> None: for t in threads: t.join() + + +@pytest.mark.esp32 +@pytest.mark.wifi_router +@pytest.mark.parametrize('config', ['sse',], indirect=True) +def test_examples_protocol_http_server_sse(dut: Dut) -> None: + # Get binary file + binary_file = os.path.join(dut.app.binary_path, 'simple.bin') + bin_size = os.path.getsize(binary_file) + logging.info('http_server_bin_size : {}KB'.format(bin_size // 1024)) + + # Upload binary and start testing + logging.info('Starting http_server simple test app') + + # Parse IP address of STA + logging.info('Waiting to connect with AP') + if dut.app.sdkconfig.get('EXAMPLE_WIFI_SSID_PWD_FROM_STDIN') is True: + dut.expect('Please input ssid password:') + env_name = 'wifi_router' + ap_ssid = get_env_config_variable(env_name, 'ap_ssid') + ap_password = get_env_config_variable(env_name, 'ap_password') + dut.write(f'{ap_ssid} {ap_password}') + got_ip = dut.expect(r'IPv4 address: (\d+\.\d+\.\d+\.\d+)[^\d]', timeout=30)[1].decode() + got_port = int(dut.expect(r"(?:[\s\S]*)Starting server on port: '(\d+)'", timeout=30)[1].decode()) + + logging.info('Got IP : {}'.format(got_ip)) + logging.info('Got Port : {}'.format(got_port)) + + # Expected Logs + dut.expect('Registering URI handlers', timeout=30) + + logging.info('Test /sse GET handler') + try: + logging.info(f'Connecting to {got_ip}:{got_port}') + conn = http.client.HTTPConnection(got_ip, got_port, timeout=15) + conn.request('GET', url='/sse') # Ensure the URL path is correct + response = conn.getresponse() + + # Process and verify only the first 5 lines of the response as the response is continuous + response_data = '' + for i, line in enumerate(response): + if i >= 5: + break + decoded_line = line.decode('utf-8').strip() + response_data += decoded_line + + conn.close() + + # Verify the format of the line + if 'data: Time since boot:' not in response_data: + raise RuntimeError(f'Unexpected line format: {response_data}') + + except Exception as e: + logging.error(f'Error during SSE GET request: {e}') + raise RuntimeError('SSE handler test failed due to connection error') diff --git a/examples/protocols/http_server/simple/sdkconfig.ci.sse b/examples/protocols/http_server/simple/sdkconfig.ci.sse new file mode 100644 index 000000000000..e34f367149fe --- /dev/null +++ b/examples/protocols/http_server/simple/sdkconfig.ci.sse @@ -0,0 +1 @@ +CONFIG_EXAMPLE_ENABLE_SSE_HANDLER=y diff --git a/tools/ci/dynamic_pipelines/constants.py b/tools/ci/dynamic_pipelines/constants.py index c0c90c4d2703..d724cbfe15af 100644 --- a/tools/ci/dynamic_pipelines/constants.py +++ b/tools/ci/dynamic_pipelines/constants.py @@ -20,6 +20,9 @@ DEFAULT_BUILD_CHILD_PIPELINE_NAME = 'Build Child Pipeline' DEFAULT_TARGET_TEST_CHILD_PIPELINE_NAME = 'Target Test Child Pipeline' +DEFAULT_TARGET_TEST_JOB_TEMPLATE_NAME = '.dynamic_target_test_template' +TIMEOUT_4H_TEMPLATE_NAME = '.timeout_4h_template' + TEST_RELATED_BUILD_JOB_NAME = 'build_test_related_apps' NON_TEST_RELATED_BUILD_JOB_NAME = 'build_non_test_related_apps' diff --git a/tools/ci/dynamic_pipelines/scripts/generate_target_test_child_pipeline.py b/tools/ci/dynamic_pipelines/scripts/generate_target_test_child_pipeline.py index 019bc789a615..d84531117081 100644 --- a/tools/ci/dynamic_pipelines/scripts/generate_target_test_child_pipeline.py +++ b/tools/ci/dynamic_pipelines/scripts/generate_target_test_child_pipeline.py @@ -19,16 +19,19 @@ from dynamic_pipelines.constants import DEFAULT_CASES_TEST_PER_JOB from dynamic_pipelines.constants import DEFAULT_TARGET_TEST_CHILD_PIPELINE_FILEPATH from dynamic_pipelines.constants import DEFAULT_TARGET_TEST_CHILD_PIPELINE_NAME +from dynamic_pipelines.constants import DEFAULT_TARGET_TEST_JOB_TEMPLATE_NAME from dynamic_pipelines.constants import DEFAULT_TEST_PATHS from dynamic_pipelines.constants import ( KNOWN_GENERATE_TEST_CHILD_PIPELINE_WARNINGS_FILEPATH, ) +from dynamic_pipelines.constants import TIMEOUT_4H_TEMPLATE_NAME from dynamic_pipelines.models import EmptyJob from dynamic_pipelines.models import Job from dynamic_pipelines.models import TargetTestJob from dynamic_pipelines.utils import dump_jobs_to_yaml from idf_build_apps import App from idf_ci.app import import_apps_from_txt +from idf_pytest.constants import TIMEOUT_4H_MARKERS from idf_pytest.script import get_pytest_cases @@ -82,7 +85,13 @@ def get_target_test_jobs( print('WARNING: excluding test cases with runner tags:', runner_tags) continue + _extends = [DEFAULT_TARGET_TEST_JOB_TEMPLATE_NAME] + for timeout_4h_marker in TIMEOUT_4H_MARKERS: + if timeout_4h_marker in env_markers: + _extends.append(TIMEOUT_4H_TEMPLATE_NAME) + target_test_job = TargetTestJob( + extends=_extends, name=f'{target_selector} - {",".join(env_markers)}', tags=runner_tags, parallel=len(cases) // DEFAULT_CASES_TEST_PER_JOB + 1, diff --git a/tools/ci/dynamic_pipelines/templates/.dynamic_jobs.yml b/tools/ci/dynamic_pipelines/templates/.dynamic_jobs.yml index 4d78137e602a..06a63fc0bde2 100644 --- a/tools/ci/dynamic_pipelines/templates/.dynamic_jobs.yml +++ b/tools/ci/dynamic_pipelines/templates/.dynamic_jobs.yml @@ -94,3 +94,6 @@ - section_start "upload_junit_reports" - run_cmd python tools/ci/artifacts_handler.py upload --type logs junit_reports - section_end "upload_junit_reports" + +.timeout_4h_template: + timeout: 4 hours diff --git a/tools/ci/idf_pytest/constants.py b/tools/ci/idf_pytest/constants.py index 1aa138b87b45..b0dbcaf5f090 100644 --- a/tools/ci/idf_pytest/constants.py +++ b/tools/ci/idf_pytest/constants.py @@ -75,6 +75,7 @@ 'twai_transceiver': 'runners with a TWAI PHY transceiver', 'flash_encryption_wifi_high_traffic': 'Flash Encryption runners with wifi high traffic support', 'ethernet': 'ethernet runner', + 'ethernet_stress': 'ethernet runner with stress test', 'ethernet_flash_8m': 'ethernet runner with 8mb flash', 'ethernet_router': 'both the runner and dut connect to the same router through ethernet NIC', 'ethernet_vlan': 'ethernet runner GARM-32-SH-1-R16S5N3', @@ -127,6 +128,11 @@ 'ram_app': 'ram_app runners', } +# by default the timeout is 1h, for some special cases we need to extend it +TIMEOUT_4H_MARKERS = [ + 'ethernet_stress', +] + DEFAULT_CONFIG_RULES_STR = ['sdkconfig.ci=default', 'sdkconfig.ci.*=', '=default'] DEFAULT_IGNORE_WARNING_FILEPATH = os.path.join(IDF_PATH, 'tools', 'ci', 'ignore_build_warnings.txt') DEFAULT_BUILD_TEST_RULES_FILEPATH = os.path.join(IDF_PATH, '.gitlab', 'ci', 'default-build-test-rules.yml') diff --git a/tools/idf_py_actions/debug_ext.py b/tools/idf_py_actions/debug_ext.py index fd56665efa75..5805e838b065 100644 --- a/tools/idf_py_actions/debug_ext.py +++ b/tools/idf_py_actions/debug_ext.py @@ -251,6 +251,10 @@ def openocd(action: str, ctx: Context, args: PropertyDict, openocd_scripts: Opti print('OpenOCD started as a background task {}'.format(process.pid)) def get_gdb_args(project_desc: Dict[str, Any], gdb_x: Tuple, gdb_ex: Tuple, gdb_commands: Optional[str]) -> List[str]: + # check if the application was built and ELF file is in place. + app_elf = os.path.join(project_desc.get('build_dir', ''), project_desc.get('app_elf', '')) + if not os.path.exists(app_elf): + raise FatalError('ELF file not found. You need to build & flash the project before running debug targets') # debugger application name (xtensa-esp32-elf-gdb, riscv32-esp-elf-gdb, ...) gdb_name = project_desc.get('monitor_toolprefix', '') + 'gdb' gdb_args = [gdb_name] diff --git a/tools/test_apps/protocols/.build-test-rules.yml b/tools/test_apps/protocols/.build-test-rules.yml index 81198acd883a..21d8dfa455b2 100644 --- a/tools/test_apps/protocols/.build-test-rules.yml +++ b/tools/test_apps/protocols/.build-test-rules.yml @@ -16,7 +16,7 @@ tools/test_apps/protocols/mqtt/publish_connect_test: temporary: true reason: lack of runners depends_components: - - esp_eth + - mqtt depends_filepatterns: - tools/ci/python_packages/common_test_methods.py - examples/common_components/**/* diff --git a/tools/test_apps/protocols/mqtt/publish_connect_test/main/Kconfig.projbuild b/tools/test_apps/protocols/mqtt/publish_connect_test/main/Kconfig.projbuild index 8e4cfe56e68e..dce9ce25303f 100644 --- a/tools/test_apps/protocols/mqtt/publish_connect_test/main/Kconfig.projbuild +++ b/tools/test_apps/protocols/mqtt/publish_connect_test/main/Kconfig.projbuild @@ -24,18 +24,6 @@ menu "Example Configuration" help URL of an mqtt broker for wss transport - config EXAMPLE_PUBLISH_TOPIC - string "publish topic" - default "/topic/publish/esp2py" - help - topic to which esp32 client publishes - - config EXAMPLE_SUBSCRIBE_TOPIC - string "subscribe topic" - default "/topic/subscribe/py2esp" - help - topic to which esp32 client subscribes (and expects data) - config EXAMPLE_BROKER_CERTIFICATE_OVERRIDE string "Broker certificate override" default "" diff --git a/tools/test_apps/protocols/mqtt/publish_connect_test/main/publish_connect_test.c b/tools/test_apps/protocols/mqtt/publish_connect_test/main/publish_connect_test.c index 23a58a67507c..d720a6d26f19 100644 --- a/tools/test_apps/protocols/mqtt/publish_connect_test/main/publish_connect_test.c +++ b/tools/test_apps/protocols/mqtt/publish_connect_test/main/publish_connect_test.c @@ -152,6 +152,8 @@ static int do_publish_setup(int argc, char **argv) { command_context.data = calloc(1, sizeof(publish_context_t)); ((publish_context_t*)command_context.data)->pattern = strdup(*publish_setup_args.pattern->sval); ((publish_context_t*)command_context.data)->pattern_repetitions = *publish_setup_args.pattern_repetitions->ival; + ((publish_context_t*)command_context.data)->subscribe_to = strdup(*publish_setup_args.subscribe_to->sval); + ((publish_context_t*)command_context.data)->publish_to = strdup(*publish_setup_args.publish_to->sval); publish_setup(&command_context, *publish_setup_args.transport->sval); return 0; } @@ -210,6 +212,8 @@ void register_common_commands(void) { } void register_publish_commands(void) { publish_setup_args.transport = arg_str1(NULL,NULL,"", "Selected transport to test"); + publish_setup_args.publish_to = arg_str1(NULL,NULL,"", "Selected publish_to to publish"); + publish_setup_args.subscribe_to = arg_str1(NULL,NULL,"", "Selected subscribe_to to publish"); publish_setup_args.pattern = arg_str1(NULL,NULL,"", "Message pattern repeated to build big messages"); publish_setup_args.pattern_repetitions = arg_int1(NULL,NULL,"", "How many times the pattern is repeated"); publish_setup_args.end = arg_end(1); @@ -220,7 +224,7 @@ void register_publish_commands(void) { publish_args.end = arg_end(1); const esp_console_cmd_t publish_setup = { .command = "publish_setup", - .help = "Run publish test\n", + .help = "Set publish test parameters\n", .hint = NULL, .func = &do_publish_setup, .argtable = &publish_setup_args diff --git a/tools/test_apps/protocols/mqtt/publish_connect_test/main/publish_connect_test.h b/tools/test_apps/protocols/mqtt/publish_connect_test/main/publish_connect_test.h index 454e82e53f85..c5311ecc2456 100644 --- a/tools/test_apps/protocols/mqtt/publish_connect_test/main/publish_connect_test.h +++ b/tools/test_apps/protocols/mqtt/publish_connect_test/main/publish_connect_test.h @@ -17,6 +17,8 @@ typedef struct { typedef struct { transport_t selected_transport; char *pattern; + char *subscribe_to; + char *publish_to; int pattern_repetitions; int qos; char *expected; @@ -41,6 +43,8 @@ typedef struct { typedef struct { struct arg_str *transport; + struct arg_str *subscribe_to; + struct arg_str *publish_to; struct arg_str *pattern; struct arg_int *pattern_repetitions; struct arg_end *end; diff --git a/tools/test_apps/protocols/mqtt/publish_connect_test/main/publish_test.c b/tools/test_apps/protocols/mqtt/publish_connect_test/main/publish_test.c index 3bdefb61af93..7d11ae31e30f 100644 --- a/tools/test_apps/protocols/mqtt/publish_connect_test/main/publish_test.c +++ b/tools/test_apps/protocols/mqtt/publish_connect_test/main/publish_test.c @@ -14,6 +14,7 @@ #include "freertos/FreeRTOS.h" #include #include "esp_system.h" +#include "esp_random.h" #include "esp_log.h" #include "mqtt_client.h" @@ -24,7 +25,7 @@ static const char *TAG = "publish_test"; static EventGroupHandle_t mqtt_event_group; const static int CONNECTED_BIT = BIT0; - +#define CLIENT_ID_SUFFIX_SIZE 12 #if CONFIG_EXAMPLE_BROKER_CERTIFICATE_OVERRIDDEN == 1 static const uint8_t mqtt_eclipseprojects_io_pem_start[] = "-----BEGIN CERTIFICATE-----\n" CONFIG_EXAMPLE_BROKER_CERTIFICATE_OVERRIDE "\n-----END CERTIFICATE-----"; #else @@ -45,8 +46,8 @@ static void mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_ case MQTT_EVENT_CONNECTED: ESP_LOGI(TAG, "MQTT_EVENT_CONNECTED"); xEventGroupSetBits(mqtt_event_group, CONNECTED_BIT); - msg_id = esp_mqtt_client_subscribe(client, CONFIG_EXAMPLE_SUBSCRIBE_TOPIC, test_data->qos); - ESP_LOGI(TAG, "sent subscribe successful %s , msg_id=%d", CONFIG_EXAMPLE_SUBSCRIBE_TOPIC, msg_id); + msg_id = esp_mqtt_client_subscribe(client, test_data->subscribe_to, test_data->qos); + ESP_LOGI(TAG, "sent subscribe successful %s , msg_id=%d", test_data->subscribe_to, msg_id); break; case MQTT_EVENT_DISCONNECTED: @@ -101,8 +102,6 @@ static void mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_ } } - - void test_init(void) { ESP_LOGI(TAG, "[APP] Free memory: %d bytes", esp_get_free_heap_size()); @@ -169,6 +168,10 @@ static void configure_client(command_context_t * ctx, const char *transport) ESP_LOGI(TAG, "Set certificate"); config.broker.verification.certificate = (const char *)mqtt_eclipseprojects_io_pem_start; } + // Generate a random client id for each iteration + char client_id[CLIENT_ID_SUFFIX_SIZE] = {0}; + snprintf(client_id, sizeof(client_id), "esp32-%08X", esp_random()); + config.credentials.client_id = client_id; esp_mqtt_set_config(ctx->mqtt_client, &config); } } @@ -200,9 +203,9 @@ void publish_test(command_context_t * ctx, int expect_to_publish, int qos, bool for (int i = 0; i < data->nr_of_msg_expected; i++) { int msg_id; if (enqueue) { - msg_id = esp_mqtt_client_enqueue(ctx->mqtt_client, CONFIG_EXAMPLE_PUBLISH_TOPIC, data->expected, data->expected_size, qos, 0, true); + msg_id = esp_mqtt_client_enqueue(ctx->mqtt_client, data->publish_to, data->expected, data->expected_size, qos, 0, true); } else { - msg_id = esp_mqtt_client_publish(ctx->mqtt_client, CONFIG_EXAMPLE_PUBLISH_TOPIC, data->expected, data->expected_size, qos, 0); + msg_id = esp_mqtt_client_publish(ctx->mqtt_client, data->publish_to, data->expected, data->expected_size, qos, 0); if(msg_id < 0) { ESP_LOGE(TAG, "Failed to publish"); break; diff --git a/tools/test_apps/protocols/mqtt/publish_connect_test/pytest_mqtt_publish_app.py b/tools/test_apps/protocols/mqtt/publish_connect_test/pytest_mqtt_publish_app.py index 763574c13e63..b2df80000254 100644 --- a/tools/test_apps/protocols/mqtt/publish_connect_test/pytest_mqtt_publish_app.py +++ b/tools/test_apps/protocols/mqtt/publish_connect_test/pytest_mqtt_publish_app.py @@ -29,24 +29,30 @@ # Publisher class creating a python client to send/receive published data from esp-mqtt client class MqttPublisher(mqtt.Client): - def __init__(self, repeat, published, publish_cfg, log_details=False): # type: (MqttPublisher, int, int, dict, bool) -> None - self.sample_string = ''.join(random.choice(string.ascii_uppercase + string.ascii_lowercase + string.digits) for _ in range(DEFAULT_MSG_SIZE)) + def __init__(self, config, log_details=False): # type: (MqttPublisher, dict, bool) -> None self.log_details = log_details - self.repeat = repeat - self.publish_cfg = publish_cfg - self.expected_data = f'{self.sample_string * self.repeat}' - self.published = published + self.config = config + self.expected_data = f'{config["pattern"] * config["scenario"]["msg_len"]}' self.received = 0 + self.subscribe_mid = 0 self.lock = Lock() self.event_client_connected = Event() + self.event_client_subscribed = Event() self.event_client_got_all = Event() - transport = 'websockets' if self.publish_cfg['transport'] in ['ws', 'wss'] else 'tcp' - super().__init__('MqttTestRunner', userdata=0, transport=transport) + transport = 'websockets' if self.config['transport'] in ['ws', 'wss'] else 'tcp' + client_id = 'MqttTestRunner' + ''.join(random.choice(string.ascii_uppercase + string.ascii_lowercase) for _ in range(5)) + super().__init__(client_id, userdata=0, transport=transport) def print_details(self, text): # type: (str) -> None if self.log_details: logging.info(text) + def on_subscribe(self, client: Any, userdata: Any, mid: Any, granted_qos: Any) -> None: + """Verify successful subscription.""" + if mid == self.subscribe_mid: + logging.info(f'Subscribed to {self.config["subscribe_topic"]} successfully with QoS: {granted_qos}') + self.event_client_subscribed.set() + def on_connect(self, mqttc: Any, obj: Any, flags: Any, rc:int) -> None: self.event_client_connected.set() @@ -56,31 +62,29 @@ def on_connect_fail(self, mqttc: Any, obj: Any) -> None: def on_message(self, mqttc: Any, userdata: Any, msg: mqtt.MQTTMessage) -> None: payload = msg.payload.decode('utf-8') if payload == self.expected_data: - userdata += 1 - self.user_data_set(userdata) - self.received = userdata - if userdata == self.published: + self.received += 1 + if self.received == self.config['scenario']['nr_of_msgs']: self.event_client_got_all.set() else: differences = len(list(filter(lambda data: data[0] != data[1], zip(payload, self.expected_data)))) logging.error(f'Payload differ in {differences} positions from expected data. received size: {len(payload)} expected size:' f'{len(self.expected_data)}') - logging.info(f'Repetitions: {payload.count(self.sample_string)}') - logging.info(f'Pattern: {self.sample_string}') - logging.info(f'First : {payload[:DEFAULT_MSG_SIZE]}') - logging.info(f'Last : {payload[-DEFAULT_MSG_SIZE:]}') + logging.info(f'Repetitions: {payload.count(self.config["pattern"])}') + logging.info(f'Pattern: {self.config["pattern"]}') + logging.info(f'First: {payload[:DEFAULT_MSG_SIZE]}') + logging.info(f'Last: {payload[-DEFAULT_MSG_SIZE:]}') matcher = difflib.SequenceMatcher(a=payload, b=self.expected_data) for match in matcher.get_matching_blocks(): logging.info(f'Match: {match}') def __enter__(self) -> Any: - qos = self.publish_cfg['qos'] - broker_host = self.publish_cfg['broker_host_' + self.publish_cfg['transport']] - broker_port = self.publish_cfg['broker_port_' + self.publish_cfg['transport']] + qos = self.config['qos'] + broker_host = self.config['broker_host_' + self.config['transport']] + broker_port = self.config['broker_port_' + self.config['transport']] try: self.print_details('Connecting...') - if self.publish_cfg['transport'] in ['ssl', 'wss']: + if self.config['transport'] in ['ssl', 'wss']: self.tls_set(None, None, None, cert_reqs=ssl.CERT_NONE, tls_version=ssl.PROTOCOL_TLSv1_2, ciphers=None) self.tls_insecure_set(True) self.event_client_connected.clear() @@ -94,7 +98,8 @@ def __enter__(self) -> Any: if not self.event_client_connected.wait(timeout=30): raise ValueError(f'ENV_TEST_FAILURE: Test script cannot connect to broker: {broker_host}') self.event_client_got_all.clear() - self.subscribe(self.publish_cfg['subscribe_topic'], qos) + result, self.subscribe_mid = self.subscribe(self.config['subscribe_topic'], qos) + assert result == 0 return self def __exit__(self, exc_type, exc_value, traceback): # type: (MqttPublisher, str, str, dict) -> None @@ -102,38 +107,47 @@ def __exit__(self, exc_type, exc_value, traceback): # type: (MqttPublisher, str self.loop_stop() -def get_configurations(dut: Dut) -> Dict[str,Any]: +def get_configurations(dut: Dut, test_case: Any) -> Dict[str,Any]: publish_cfg = {} try: @no_type_check - def get_broker_from_dut(dut, config_option): + def get_config_from_dut(dut, config_option): # logging.info('Option:', config_option, dut.app.sdkconfig.get(config_option)) value = re.search(r'\:\/\/([^:]+)\:([0-9]+)', dut.app.sdkconfig.get(config_option)) if value is None: return None, None return value.group(1), int(value.group(2)) # Get publish test configuration - publish_cfg['publish_topic'] = dut.app.sdkconfig.get('EXAMPLE_SUBSCRIBE_TOPIC').replace('"','') - publish_cfg['subscribe_topic'] = dut.app.sdkconfig.get('EXAMPLE_PUBLISH_TOPIC').replace('"','') - publish_cfg['broker_host_ssl'], publish_cfg['broker_port_ssl'] = get_broker_from_dut(dut, 'EXAMPLE_BROKER_SSL_URI') - publish_cfg['broker_host_tcp'], publish_cfg['broker_port_tcp'] = get_broker_from_dut(dut, 'EXAMPLE_BROKER_TCP_URI') - publish_cfg['broker_host_ws'], publish_cfg['broker_port_ws'] = get_broker_from_dut(dut, 'EXAMPLE_BROKER_WS_URI') - publish_cfg['broker_host_wss'], publish_cfg['broker_port_wss'] = get_broker_from_dut(dut, 'EXAMPLE_BROKER_WSS_URI') + publish_cfg['broker_host_ssl'], publish_cfg['broker_port_ssl'] = get_config_from_dut(dut, 'EXAMPLE_BROKER_SSL_URI') + publish_cfg['broker_host_tcp'], publish_cfg['broker_port_tcp'] = get_config_from_dut(dut, 'EXAMPLE_BROKER_TCP_URI') + publish_cfg['broker_host_ws'], publish_cfg['broker_port_ws'] = get_config_from_dut(dut, 'EXAMPLE_BROKER_WS_URI') + publish_cfg['broker_host_wss'], publish_cfg['broker_port_wss'] = get_config_from_dut(dut, 'EXAMPLE_BROKER_WSS_URI') except Exception: logging.info('ENV_TEST_FAILURE: Some mandatory PUBLISH test case not found in sdkconfig') raise + transport, qos, enqueue, scenario = test_case + if publish_cfg['broker_host_' + transport] is None: + pytest.skip(f'Skipping transport: {transport}...') + publish_cfg['scenario'] = scenario + publish_cfg['qos'] = qos + publish_cfg['enqueue'] = enqueue + publish_cfg['transport'] = transport + publish_cfg['pattern'] = ''.join(random.choice(string.ascii_uppercase + string.ascii_lowercase + string.digits) for _ in range(DEFAULT_MSG_SIZE)) + publish_cfg['test_timeout'] = get_timeout(test_case) + unique_topic = ''.join(random.choice(string.ascii_uppercase + string.ascii_lowercase) for _ in range(DEFAULT_MSG_SIZE)) + publish_cfg['subscribe_topic'] = 'test/subscribe_to/' + unique_topic + publish_cfg['publish_topic'] = 'test/subscribe_to/' + unique_topic logging.info(f'configuration: {publish_cfg}') return publish_cfg @contextlib.contextmanager -def connected_and_subscribed(dut:Dut, transport:str, pattern:str, pattern_repetitions:int) -> Any: - dut.write(f'publish_setup {transport} {pattern} {pattern_repetitions}') - dut.write(f'start') +def connected_and_subscribed(dut:Dut) -> Any: + dut.write('start') dut.expect(re.compile(rb'MQTT_EVENT_SUBSCRIBED'), timeout=60) yield - dut.write(f'stop') + dut.write('stop') def get_scenarios() -> List[Dict[str, int]]: @@ -147,112 +161,112 @@ def get_scenarios() -> List[Dict[str, int]]: continue break if not scenarios: # No message sizes present in the env - set defaults - scenarios = [{'len':0, 'repeat':5}, # zero-sized messages - {'len':2, 'repeat':5}, # short messages - {'len':200, 'repeat':3}, # long messages + scenarios = [{'msg_len':0, 'nr_of_msgs':5}, # zero-sized messages + {'msg_len':2, 'nr_of_msgs':5}, # short messages + {'msg_len':200, 'nr_of_msgs':3}, # long messages ] return scenarios def get_timeout(test_case: Any) -> int: transport, qos, enqueue, scenario = test_case - if transport in ['ws', 'wss'] or qos == 2: - return 120 - return 60 - - -def run_publish_test_case(dut: Dut, test_case: Any, publish_cfg: Any) -> None: - transport, qos, enqueue, scenario = test_case - if publish_cfg['broker_host_' + transport] is None: - pytest.skip(f'Skipping transport: {transport}...') - repeat = scenario['len'] - published = scenario['repeat'] - publish_cfg['qos'] = qos - publish_cfg['queue'] = enqueue - publish_cfg['transport'] = transport - test_timeout = get_timeout(test_case) - logging.info(f'Starting Publish test: transport:{transport}, qos:{qos}, nr_of_msgs:{published},' - f' msg_size:{repeat*DEFAULT_MSG_SIZE}, enqueue:{enqueue}') - with MqttPublisher(repeat, published, publish_cfg) as publisher, connected_and_subscribed(dut, transport, publisher.sample_string, scenario['len']): + timeout = int(scenario['nr_of_msgs'] * 20) + if qos == 1: + timeout += 30 + if qos == 2: + timeout += 45 + if transport in ['ws', 'wss']: + timeout += 30 + return timeout + + +def run_publish_test_case(dut: Dut, config: Any) -> None: + logging.info(f'Starting Publish test: transport:{config["transport"]}, qos:{config["qos"]},' + f'nr_of_msgs:{config["scenario"]["nr_of_msgs"]},' + f' msg_size:{config["scenario"]["msg_len"] * DEFAULT_MSG_SIZE}, enqueue:{config["enqueue"]}') + dut.write(f'publish_setup {config["transport"]} {config["publish_topic"]} {config["subscribe_topic"]} {config["pattern"]} {config["scenario"]["msg_len"]}') + with MqttPublisher(config) as publisher, connected_and_subscribed(dut): + assert publisher.event_client_subscribed.wait(timeout=config['test_timeout']), 'Runner failed to subscribe' msgs_published: List[mqtt.MQTTMessageInfo] = [] - dut.write(f'publish {publisher.published} {qos} {enqueue}') - assert publisher.event_client_got_all.wait(timeout=test_timeout), (f'Not all data received from ESP32: {transport} ' - f'qos={qos} received: {publisher.received} ' - f'expected: {publisher.published}') + dut.write(f'publish {config["scenario"]["nr_of_msgs"]} {config["qos"]} {config["enqueue"]}') + assert publisher.event_client_got_all.wait(timeout=config['test_timeout']), (f'Not all data received from ESP32: {config["transport"]} ' + f'qos={config["qos"]} received: {publisher.received} ' + f'expected: {config["scenario"]["nr_of_msgs"]}') logging.info(' - all data received from ESP32') - payload = publisher.sample_string * publisher.repeat - for _ in range(publisher.published): + payload = config['pattern'] * config['scenario']['msg_len'] + for _ in range(config['scenario']['nr_of_msgs']): with publisher.lock: - msg = publisher.publish(topic=publisher.publish_cfg['publish_topic'], payload=payload, qos=qos) - if qos > 0: + msg = publisher.publish(topic=config['publish_topic'], payload=payload, qos=config['qos']) + if config['qos'] > 0: msgs_published.append(msg) logging.info(f'Published: {len(msgs_published)}') while msgs_published: - msgs_published = [msg for msg in msgs_published if msg.is_published()] + msgs_published = [msg for msg in msgs_published if not msg.is_published()] + + logging.info('All messages from runner published') try: - dut.expect(re.compile(rb'Correct pattern received exactly x times'), timeout=test_timeout) + dut.expect(re.compile(rb'Correct pattern received exactly x times'), timeout=config['test_timeout']) except pexpect.exceptions.ExceptionPexpect: - dut.write(f'publish_report') + dut.write('publish_report') dut.expect(re.compile(rb'Test Report'), timeout=30) raise logging.info('ESP32 received all data from runner') -stress_scenarios = [{'len':20, 'repeat':50}] # many medium sized +stress_scenarios = [{'msg_len':20, 'nr_of_msgs':30}] # many medium sized transport_cases = ['tcp', 'ws', 'wss', 'ssl'] qos_cases = [0, 1, 2] enqueue_cases = [0, 1] local_broker_supported_transports = ['tcp'] -local_broker_scenarios = [{'len':0, 'repeat':5}, # zero-sized messages - {'len':5, 'repeat':20}, # short messages - {'len':500, 'repeat':10}, # long messages - {'len':20, 'repeat':20}] # many medium sized +local_broker_scenarios = [{'msg_len':0, 'nr_of_msgs':5}, # zero-sized messages + {'msg_len':5, 'nr_of_msgs':20}, # short messages + {'msg_len':500, 'nr_of_msgs':10}, # long messages + {'msg_len':20, 'nr_of_msgs':20}] # many medium sized -def make_cases(scenarios: List[Dict[str, int]]) -> List[Tuple[str, int, int, Dict[str, int]]]: - return [test_case for test_case in product(transport_cases, qos_cases, enqueue_cases, scenarios)] +def make_cases(transport: Any, scenarios: List[Dict[str, int]]) -> List[Tuple[str, int, int, Dict[str, int]]]: + return [test_case for test_case in product(transport, qos_cases, enqueue_cases, scenarios)] -test_cases = make_cases(get_scenarios()) -stress_test_cases = make_cases(stress_scenarios) +test_cases = make_cases(transport_cases, get_scenarios()) +stress_test_cases = make_cases(transport_cases, stress_scenarios) @pytest.mark.esp32 @pytest.mark.ethernet -@pytest.mark.nightly_run @pytest.mark.parametrize('test_case', test_cases) @pytest.mark.parametrize('config', ['default'], indirect=True) def test_mqtt_publish(dut: Dut, test_case: Any) -> None: - publish_cfg = get_configurations(dut) + publish_cfg = get_configurations(dut, test_case) dut.expect(re.compile(rb'mqtt>'), timeout=30) dut.confirm_write('init', expect_pattern='init', timeout=30) - run_publish_test_case(dut, test_case, publish_cfg) + run_publish_test_case(dut, publish_cfg) @pytest.mark.esp32 -@pytest.mark.ethernet +@pytest.mark.ethernet_stress @pytest.mark.nightly_run @pytest.mark.parametrize('test_case', stress_test_cases) @pytest.mark.parametrize('config', ['default'], indirect=True) def test_mqtt_publish_stress(dut: Dut, test_case: Any) -> None: - publish_cfg = get_configurations(dut) + publish_cfg = get_configurations(dut, test_case) dut.expect(re.compile(rb'mqtt>'), timeout=30) dut.write('init') - run_publish_test_case(dut, test_case, publish_cfg) + run_publish_test_case(dut, publish_cfg) @pytest.mark.esp32 @pytest.mark.ethernet -@pytest.mark.parametrize('test_case', make_cases(local_broker_scenarios)) +@pytest.mark.parametrize('test_case', make_cases(local_broker_supported_transports, local_broker_scenarios)) @pytest.mark.parametrize('config', ['local_broker'], indirect=True) -def test_mqtt_publish_lcoal(dut: Dut, test_case: Any) -> None: +def test_mqtt_publish_local(dut: Dut, test_case: Any) -> None: if test_case[0] not in local_broker_supported_transports: pytest.skip(f'Skipping transport: {test_case[0]}...') dut_ip = dut.expect(r'esp_netif_handlers: .+ ip: (\d+\.\d+\.\d+\.\d+),').group(1) - publish_cfg = get_configurations(dut) + publish_cfg = get_configurations(dut, test_case) publish_cfg['broker_host_tcp'] = dut_ip publish_cfg['broker_port_tcp'] = 1234 dut.expect(re.compile(rb'mqtt>'), timeout=30) dut.confirm_write('init', expect_pattern='init', timeout=30) - run_publish_test_case(dut, test_case, publish_cfg) + run_publish_test_case(dut, publish_cfg)