diff --git a/.github/workflows/upload_component.yml b/.github/workflows/upload_component.yml index 408274344..3f36193ed 100644 --- a/.github/workflows/upload_component.yml +++ b/.github/workflows/upload_component.yml @@ -18,6 +18,7 @@ jobs: directories: > components/audio/pwm_audio; components/bluetooth/ble_conn_mgr; + components/bluetooth/ble_profiles/std/ble_anp; components/bluetooth/ble_profiles/std/ble_hrp; components/bluetooth/ble_profiles/esp/ble_ota; components/bootloader_support_plus; diff --git a/.gitlab/ci/build.yml b/.gitlab/ci/build.yml index ff394865f..3c258f59d 100644 --- a/.gitlab/ci/build.yml +++ b/.gitlab/ci/build.yml @@ -98,6 +98,19 @@ build_example_bluetooth_ble_conn_mgr_ble_spp_server: variables: EXAMPLE_DIR: examples/bluetooth/ble_conn_mgr/ble_spp/spp_server +build_example_bluetooth_ble_anp: + extends: + - .build_examples_template + - .rules:build:example_bluetooth_ble_anp + parallel: + matrix: + - IMAGE: espressif/idf:release-v4.3 + - IMAGE: espressif/idf:release-v4.4 + - IMAGE: espressif/idf:release-v5.0 + - IMAGE: espressif/idf:release-v5.1 + variables: + EXAMPLE_DIR: examples/bluetooth/ble_profiles/ble_anp + build_example_bluetooth_ble_hrp: extends: - .build_examples_template diff --git a/.gitlab/ci/rules.yml b/.gitlab/ci/rules.yml index d1405d176..1fd713602 100644 --- a/.gitlab/ci/rules.yml +++ b/.gitlab/ci/rules.yml @@ -20,6 +20,10 @@ - "components/bluetooth/ble_conn_mgr/**/*" - "components/tools/cmake_utilities/package_manager.cmake" +.patterns-components_bluetooth_ble_profiles_std_ble_anp: &patterns-components_bluetooth_ble_profiles_std_ble_anp + - "components/bluetooth/ble_profiles/std/ble_anp/**/*" + - "components/tools/cmake_utilities/package_manager.cmake" + .patterns-components_bluetooth_ble_profiles_std_ble_hrp: &patterns-components_bluetooth_ble_profiles_std_ble_hrp - "components/bluetooth/ble_profiles/std/ble_hrp/**/*" - "components/tools/cmake_utilities/package_manager.cmake" @@ -251,6 +255,9 @@ .patterns-example_bluetooth_ble_conn_mgr: &patterns-example_bluetooth_ble_conn_mgr - "examples/bluetooth/ble_conn_mgr/**/*" +.patterns-example_bluetooth_ble_anp: &patterns-example_bluetooth_ble_anp + - "examples/bluetooth/ble_anp/**/*" + .patterns-example_bluetooth_ble_hrp: &patterns-example_bluetooth_ble_hrp - "examples/bluetooth/ble_hrp/**/*" @@ -457,6 +464,17 @@ - <<: *if-dev-push changes: *patterns-example_bluetooth_ble_conn_mgr +.rules:build:example_bluetooth_ble_anp: + rules: + - <<: *if-protected + - <<: *if-label-build + - <<: *if-dev-push + changes: *patterns-build_system + - <<: *if-dev-push + changes: *patterns-components_bluetooth_ble_profiles_std_ble_anp + - <<: *if-dev-push + changes: *patterns-example_bluetooth_ble_anp + .rules:build:example_bluetooth_ble_hrp: rules: - <<: *if-protected diff --git a/components/bluetooth/ble_conn_mgr/src/esp_nimble.c b/components/bluetooth/ble_conn_mgr/src/esp_nimble.c index b810181dc..e9741721e 100644 --- a/components/bluetooth/ble_conn_mgr/src/esp_nimble.c +++ b/components/bluetooth/ble_conn_mgr/src/esp_nimble.c @@ -1217,7 +1217,6 @@ static int esp_ble_conn_gap_event(struct ble_gap_event *event, void *arg) struct ble_hs_adv_fields fields; rc = ble_hs_adv_parse_fields(&fields, event->disc.data, event->disc.length_data); if (rc != ESP_OK) { - ESP_LOGE(TAG, ""); return 0; } else { /* Try to connect to the advertiser if it looks interesting. */ diff --git a/components/bluetooth/ble_profiles/std/ble_anp/CHANGELOG.md b/components/bluetooth/ble_profiles/std/ble_anp/CHANGELOG.md new file mode 100644 index 000000000..88acff28f --- /dev/null +++ b/components/bluetooth/ble_profiles/std/ble_anp/CHANGELOG.md @@ -0,0 +1,6 @@ +## v0.1.0 + +This is the first release version for Alert Notification Profile component in Espressif Component Registry, more detailed descriptions about the project, please refer to [User_Guide](https://docs.espressif.com/projects/espressif-esp-iot-solution/en/latest/bluetooth/ble_profiles.html). + +Features: +- ANP: Support Alert Notification Profile. diff --git a/components/bluetooth/ble_profiles/std/ble_anp/CMakeLists.txt b/components/bluetooth/ble_profiles/std/ble_anp/CMakeLists.txt new file mode 100644 index 000000000..0c1329789 --- /dev/null +++ b/components/bluetooth/ble_profiles/std/ble_anp/CMakeLists.txt @@ -0,0 +1,16 @@ +set(srcs "") +set(req "") +set(include "") +set(priv_includes "") +set(priv_req "ble_conn_mgr") + +if(CONFIG_BLE_ALERT_NOTIFICATION_PROFILES) +list(APPEND srcs "src/esp_anp.c") +list(APPEND include "include") +endif() + +idf_component_register(SRCS "${srcs}" + INCLUDE_DIRS "${include}" + PRIV_INCLUDE_DIRS "${priv_includes}" + REQUIRES "${req}" + PRIV_REQUIRES "${priv_req}") diff --git a/components/bluetooth/ble_profiles/std/ble_anp/Kconfig b/components/bluetooth/ble_profiles/std/ble_anp/Kconfig new file mode 100644 index 000000000..6e24051f0 --- /dev/null +++ b/components/bluetooth/ble_profiles/std/ble_anp/Kconfig @@ -0,0 +1,7 @@ +menu "BLE Profile: Alert Notification" + config BLE_ALERT_NOTIFICATION_PROFILES + bool "Enable Alert Notification Profile" + default n + help + Enable support for Alert Notification Profile. +endmenu diff --git a/components/bluetooth/ble_profiles/std/ble_anp/README.md b/components/bluetooth/ble_profiles/std/ble_anp/README.md new file mode 100644 index 000000000..9e479e1f1 --- /dev/null +++ b/components/bluetooth/ble_profiles/std/ble_anp/README.md @@ -0,0 +1,50 @@ +## Alert Notification Profile Component + +[![Component Registry](https://components.espressif.com/components/espressif/ble_anp/badge.svg)](https://components.espressif.com/components/espressif/ble_anp) + +- [User Guide](https://docs.espressif.com/projects/espressif-esp-iot-solution/en/latest/bluetooth/ble_profiles.html) + +``ble_anp`` is a component which provide a simplified API interface for accessing the commonly used BLE alert notification profile functionality on a GATT Client. + +### Add component to your project + +Please use the component manager command `add-dependency` to add the `ble_anp` to your project's dependency, during the `CMake` step the component will be downloaded automatically + +``` +idf.py add-dependency "espressif/ble_anp=*" +``` + +### Examples + +Please use the component manager command `create-project-from-example` to create the project from example template + +``` +idf.py create-project-from-example "espressif/ble_anp=*:ble_anp" +``` + +Then the example will be downloaded in current folder, you can check into it for build and flash. + +> You can use this command to download other examples. Or you can download examples from esp-iot-solution repository: +1. [ble_anp](https://github.com/espressif/esp-iot-solution/tree/master/examples/bluetooth/ble_profiles/ble_anp) + +### Q&A + +Q1. I encountered the following problems when using the package manager + +``` + HINT: Please check manifest file of the following component(s): main + + ERROR: Because project depends on esp-now (2.*) which doesn't match any + versions, version solving failed. +``` + +A1. For the examples downloaded by using this command, you need to comment out the override_path line in the main/idf_component.yml of each example. + +Q2. I encountered the following problems when using the package manager + +``` +Executing action: create-project-from-example +CMakeLists.txt not found in project directory /home/username +``` + +A2. This is because an older version packege manager was used, please run `pip install -U idf-component-manager` in ESP-IDF environment to update. diff --git a/components/bluetooth/ble_profiles/std/ble_anp/idf_component.yml b/components/bluetooth/ble_profiles/std/ble_anp/idf_component.yml new file mode 100644 index 000000000..82c692b84 --- /dev/null +++ b/components/bluetooth/ble_profiles/std/ble_anp/idf_component.yml @@ -0,0 +1,6 @@ +version: "0.1.0" +description: BLE standard profile support Alert Notification +url: https://github.com/espressif/esp-iot-solution/tree/master/components/bluetooth/ble_profiles/std/ble_anp +issues: https://github.com/espressif/esp-iot-solution/issues +examples: + - path: ../../../../../examples/bluetooth/ble_profiles diff --git a/components/bluetooth/ble_profiles/std/ble_anp/include/esp_anp.h b/components/bluetooth/ble_profiles/std/ble_anp/include/esp_anp.h new file mode 100644 index 000000000..4bb006714 --- /dev/null +++ b/components/bluetooth/ble_profiles/std/ble_anp/include/esp_anp.h @@ -0,0 +1,184 @@ +/* + * SPDX-FileCopyrightText: 2019-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "esp_event.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** @cond **/ +/* BLE ANP EVENTS BASE */ +ESP_EVENT_DECLARE_BASE(BLE_ANP_EVENTS); +/** @endcond **/ + +/* 16 Bit Alert Notification Service UUID */ +#define BLE_ANP_UUID16 0x1811 + +/* 16 Bit Alert Notification Service Characteristic UUIDs */ +#define BLE_ANP_CHR_UUID16_SUP_NEW_ALERT_CAT 0x2A47 +#define BLE_ANP_CHR_UUID16_NEW_ALERT 0x2A46 +#define BLE_ANP_CHR_UUID16_SUP_UNR_ALERT_CAT 0x2A48 +#define BLE_ANP_CHR_UUID16_UNR_ALERT_STAT 0x2A45 +#define BLE_ANP_CHR_UUID16_ALERT_NOT_CTRL_PT 0x2A44 + +/* Alert Notification Service Category ID Bit Masks + * + * TODO: Add remaining 2 optional categories */ +#define BLE_ANP_CAT_BM_NONE 0x00 +#define BLE_ANP_CAT_BM_SIMPLE_ALERT 0x01 +#define BLE_ANP_CAT_BM_EMAIL 0x02 +#define BLE_ANP_CAT_BM_NEWS 0x04 +#define BLE_ANP_CAT_BM_CALL 0x08 +#define BLE_ANP_CAT_BM_MISSED_CALL 0x10 +#define BLE_ANP_CAT_BM_SMS 0x20 +#define BLE_ANP_CAT_BM_VOICE_MAIL 0x40 +#define BLE_ANP_CAT_BM_SCHEDULE 0x80 + +/* Alert Notification Service Category IDs + * + * TODO: Add remaining 2 optional categories */ +#define BLE_ANP_CAT_ID_SIMPLE_ALERT 0 +#define BLE_ANP_CAT_ID_EMAIL 1 +#define BLE_ANP_CAT_ID_NEWS 2 +#define BLE_ANP_CAT_ID_CALL 3 +#define BLE_ANP_CAT_ID_MISSED_CALL 4 +#define BLE_ANP_CAT_ID_SMS 5 +#define BLE_ANP_CAT_ID_VOICE_MAIL 6 +#define BLE_ANP_CAT_ID_SCHEDULE 7 + +/* Number of valid ANS categories + * + * TODO: Add remaining 2 optional categories */ +#define BLE_ANP_CAT_NUM 8 + +/* Alert Notification Control Point Command IDs */ +#define BLE_ANP_CMD_EN_NEW_ALERT_CAT 0 +#define BLE_ANP_CMD_EN_UNR_ALERT_CAT 1 +#define BLE_ANP_CMD_DIS_NEW_ALERT_CAT 2 +#define BLE_ANP_CMD_DIS_UNR_ALERT_CAT 3 +#define BLE_ANP_CMD_NOT_NEW_ALERT_IMMEDIATE 4 +#define BLE_ANP_CMD_NOT_UNR_ALERT_IMMEDIATE 5 + +/* Max length of new alert info string */ +#define BLE_ANP_INFO_STR_MAX_LEN 18 + +/* Max length of a new alert notification, max string length + 2 bytes for category ID and count. */ +#define BLE_ANP_NEW_ALERT_MAX_LEN (BLE_ANP_INFO_STR_MAX_LEN + 2) + +/** + * @brief The status of the new or unread alert + */ +typedef struct { + union { + struct { + uint8_t cat_id; /*!< The predefined categories of unread alerts and messages */ + uint8_t count; /*!< The number of unread alerts in the server ranging from 0 to 255 */ + } unr_alert_stat; /*!< The status of unread alerts */ + + struct { + uint8_t cat_id; /*!< The predefined categories of new alerts and messages */ + uint8_t count; /*!< The number of new alerts in the server ranging from 0 to 255 */ + uint8_t cat_info[BLE_ANP_INFO_STR_MAX_LEN]; /*!< The brief text information for the last alert */ + } new_alert_val; /*!< The status of new alerts */ + }; /*!< Alert notification status */ +} esp_ble_anp_data_t; + +/** + * @brief The option of the new or unread alert + */ +typedef enum { + BLE_ANP_OPT_ENABLE, + BLE_ANP_OPT_DISABLE, + BLE_ANP_OPT_RECOVER, +} esp_ble_anp_option_t; + +/** + * @brief Read the value of or check supported new alert category. + * + * @attention 1. When cat_id is 0xFF, read the value of supported new alert category. + * @attention 2. When cat_id isn't 0xFF, check supported new alert category is enable or disable. + * + * @param[in] cat_id The ID of the category to read or check + * @param[out] cat_val The value of read or check supported new alert category + * + * @return + * - ESP_OK on successful + * - ESP_ERR_INVALID_ARG on wrong category of the alert + */ +esp_err_t esp_ble_anp_get_new_alert(uint8_t cat_id, uint8_t *cat_val); + +/** + * @brief Request or recovery supported new alert notification to the given category. + * + * @attention 1. When cat_id is 0xFF, recover for all supported new alert category to get the current message counts. + * @attention 2. When cat_id isn't 0xFF, request for a supported new alert category to get the current message counts. + * + * @param[in] cat_id The ID of the category to request or recover the notification to + * @param[in] option Disable or enable supported new alert category + * + * @return + * - ESP_OK on successful + * - ESP_ERR_INVALID_ARG on wrong category of the alert + */ +esp_err_t esp_ble_anp_set_new_alert(uint8_t cat_id, esp_ble_anp_option_t option); + +/** + * @brief Read the value of or check supported unread alert status category. + * + * @attention 1. When cat_id is 0xFF, read the value of supported unread alert status category. + * @attention 2. When cat_id isn't 0xFF, check supported unread alert status category is enable or disable. + * + * @param[in] cat_id The ID of the category to read or check + * @param[out] cat_val The value of read or check supported unread alert status category + * + * @return + * - ESP_OK on successful + * - ESP_ERR_INVALID_ARG on wrong category of the alert + */ +esp_err_t esp_ble_anp_get_unr_alert(uint8_t cat_id, uint8_t *cat_val); + +/** + * @brief Request or recovery supported unread alert status notification to the given category. + * + * @attention 1. When cat_id is 0xFF, recover for all supported unread alert status category to get the current message counts. + * @attention 2. When cat_id isn't 0xFF, request for an supported unread alert status category to get the current message counts. + * + * @param[in] cat_id The ID of the category to request or recover the notification to + * @param[in] option Disable or enable supported unread alert status category + * + * @return + * - ESP_OK on successful + * - ESP_ERR_INVALID_ARG on wrong category of the alert + */ +esp_err_t esp_ble_anp_set_unr_alert(uint8_t cat_id, esp_ble_anp_option_t option); + +/** + * @brief Initialization GATT Alert Notification Profile + * + * @return + * - ESP_OK on successful + * - ESP_ERR_INVALID_ARG on wrong initialization + * - ESP_FAIL on error + */ +esp_err_t esp_ble_anp_init(void); + +/** + * @brief Deinitialization GATT Alert Notification Profile + * + * @return + * - ESP_OK on successful + * - ESP_ERR_INVALID_ARG on wrong initialization + * - ESP_FAIL on error + */ +esp_err_t esp_ble_anp_deinit(void); + +#ifdef __cplusplus +} +#endif diff --git a/components/bluetooth/ble_profiles/std/ble_anp/license.txt b/components/bluetooth/ble_profiles/std/ble_anp/license.txt new file mode 100644 index 000000000..d64569567 --- /dev/null +++ b/components/bluetooth/ble_profiles/std/ble_anp/license.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/components/bluetooth/ble_profiles/std/ble_anp/src/esp_anp.c b/components/bluetooth/ble_profiles/std/ble_anp/src/esp_anp.c new file mode 100644 index 000000000..14c98a059 --- /dev/null +++ b/components/bluetooth/ble_profiles/std/ble_anp/src/esp_anp.c @@ -0,0 +1,376 @@ +/* + * SPDX-FileCopyrightText: 2019-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** @file + * @brief Alert Notification Profile + */ + +#include + +#include "esp_log.h" + +#include "esp_ble_conn_mgr.h" +#include "esp_anp.h" + +static const char* TAG = "esp_anp"; + +/* Supported categories bitmasks */ +static uint8_t ble_svc_ans_new_alert_cat; +static uint8_t ble_svc_ans_unr_alert_cat; + +/* Characteristic values */ +static uint8_t ble_svc_ans_new_alert_val[BLE_ANP_NEW_ALERT_MAX_LEN]; +static uint8_t ble_svc_ans_unr_alert_stat[2]; +static uint8_t ble_svc_ans_alert_not_ctrl_pt[2]; + +/* Alert counts, one value for each category */ +static uint8_t ble_svc_ans_new_alert_cnt[BLE_ANP_CAT_NUM]; +static uint8_t ble_svc_ans_unr_alert_cnt[BLE_ANP_CAT_NUM]; + +ESP_EVENT_DEFINE_BASE(BLE_ANP_EVENTS); + +//4.09 Configure Alert Notification Control Point +static esp_err_t esp_ble_anp_alert_ctrl_set(uint8_t cmd_id, uint8_t cat_id) +{ + /* ANS Control point command and catagory variables */ + uint8_t cat_bit_mask; + + esp_ble_conn_data_t inbuff = { + .type = BLE_CONN_UUID_TYPE_16, + .uuid = { + .uuid16 = BLE_ANP_CHR_UUID16_ALERT_NOT_CTRL_PT, + }, + .data = ble_svc_ans_alert_not_ctrl_pt, + .data_len = sizeof(ble_svc_ans_alert_not_ctrl_pt), + }; + + ble_svc_ans_alert_not_ctrl_pt[0] = cmd_id; + ble_svc_ans_alert_not_ctrl_pt[1] = cat_id; + + /* Get command ID and category ID */ + cmd_id = ble_svc_ans_alert_not_ctrl_pt[0]; + cat_id = ble_svc_ans_alert_not_ctrl_pt[1]; + + /* Set cat_bit_mask to the appropriate bitmask based on cat_id */ + if (cat_id < BLE_ANP_CAT_NUM) { + cat_bit_mask = (1 << cat_id); + } else if (cat_id == 0xFF) { + cat_bit_mask = cat_id; + } else { + /* invalid category ID */ + return ESP_ERR_INVALID_ARG; + } + + switch (cmd_id) { + case BLE_ANP_CMD_EN_NEW_ALERT_CAT: + ble_svc_ans_new_alert_cat |= cat_bit_mask; + break; + case BLE_ANP_CMD_EN_UNR_ALERT_CAT: + ble_svc_ans_unr_alert_cat |= cat_bit_mask; + break; + case BLE_ANP_CMD_DIS_NEW_ALERT_CAT: + ble_svc_ans_new_alert_cat &= ~cat_bit_mask; + break; + case BLE_ANP_CMD_DIS_UNR_ALERT_CAT: + ble_svc_ans_unr_alert_cat &= ~cat_bit_mask; + break; + case BLE_ANP_CMD_NOT_NEW_ALERT_IMMEDIATE: + if (cat_id == 0xFF) { + /* If cat_id is 0xFF, notify on all enabled categories */ + for (int i = BLE_ANP_CAT_NUM - 1; i > 0; --i) { + if ((ble_svc_ans_new_alert_cat >> i) & 0x01) { + ESP_LOGI(TAG, "Request the New Alert in the server to notify immediately on all enabled categories %d", i); + } + } + } else { + ESP_LOGI(TAG, "Request the New Alert in the server to notify immediately on current enabled categories %d", cat_id); + } + break; + case BLE_ANP_CMD_NOT_UNR_ALERT_IMMEDIATE: + if (cat_id == 0xFF) { + /* If cat_id is 0xFF, notify on all enabled categories */ + for (int i = BLE_ANP_CAT_NUM - 1; i > 0; --i) { + if ((ble_svc_ans_unr_alert_cat >> i) & 0x01) { + ESP_LOGI(TAG, "Request the Unread Alert Status in the server to notify immediately on all enabled categories %d", i); + } + } + } else { + ESP_LOGI(TAG, "Request the Unread Alert Status in the server to notify immediately on current enabled categories %d", cat_id); + } + break; + default: + return ESP_ERR_INVALID_ARG; + } + + esp_err_t rc = esp_ble_conn_write(&inbuff); + if (rc == 0) { + ESP_LOGI(TAG, "Configure Alert Notification Control Point Success!"); + ESP_LOG_BUFFER_HEX_LEVEL(TAG, inbuff.data, inbuff.data_len, ESP_LOG_INFO); + } + + return rc; +} + +static esp_err_t esp_ble_anp_handle_new_alert(const uint8_t *inbuf, uint16_t inlen) +{ + if (!inbuf || inlen < 2) { + return ESP_ERR_INVALID_ARG; + } + + uint8_t cat_id = inbuf[0]; + uint8_t cat_bit_mask; + const char *cat_info = (inlen > 2) ? (const char *)(inbuf + (inlen - 2)) : NULL; + + if (cat_id < BLE_ANP_CAT_NUM) { + cat_bit_mask = (1 << cat_id); + } else { + return ESP_ERR_INVALID_ARG; + } + + if ((cat_bit_mask & ble_svc_ans_new_alert_cat) == 0) { + return ESP_ERR_INVALID_ARG; + } + + /* 4.5 Receive Notification of New Alert */ + ble_svc_ans_new_alert_cnt[cat_id] = inbuf[1]; + + /* Set ID and count values */ + ble_svc_ans_new_alert_val[0] = cat_id; + ble_svc_ans_new_alert_val[1] = ble_svc_ans_new_alert_cnt[cat_id]; + + ESP_LOGI(TAG, "Receive Notification of New Alert on cat_id %d which count change to %d", ble_svc_ans_new_alert_val[0], ble_svc_ans_new_alert_val[1]); + + if (cat_info) { + /* If cat_info is longer than the max string length only write up to the maximum length */ + memcpy(&ble_svc_ans_new_alert_val[2], cat_info, MIN(strlen(cat_info), BLE_ANP_INFO_STR_MAX_LEN)); + } + + return ESP_OK; +} + +//4.7 Receive Notification of Unread Alert Status characteristic +static esp_err_t esp_ble_anp_handle_unr_alert(const uint8_t *inbuf, uint16_t inlen) +{ + if (!inbuf || inlen != 2) { + ESP_LOGE(TAG, "Receive Notification of Unread Alert Status Must include category ID and Status"); + return ESP_FAIL; + } + + uint8_t cat_id = inbuf[0]; + uint8_t cat_bit_mask; + + if (cat_id < BLE_ANP_CAT_NUM) { + cat_bit_mask = 1 << cat_id; + } else { + return ESP_ERR_INVALID_ARG; + } + + if ((cat_bit_mask & ble_svc_ans_unr_alert_cat) == 0) { + return ESP_ERR_INVALID_ARG; + } + + ble_svc_ans_unr_alert_cnt[cat_id] = inbuf[1]; + + ble_svc_ans_unr_alert_stat[0] = cat_id; + ble_svc_ans_unr_alert_stat[1] = ble_svc_ans_unr_alert_cnt[cat_id]; + + ESP_LOGI(TAG, "Receive Notification of Unread Alert Status on cat_id %d which status change to %d", ble_svc_ans_unr_alert_stat[0], ble_svc_ans_unr_alert_stat[1]); + + return ESP_OK;; +} + +static void esp_ble_anp_event(void *handler_args, esp_event_base_t base, int32_t id, void *event_data) +{ + if (base != BLE_CONN_MGR_EVENTS) { + return; + } + + switch (id) { + case ESP_BLE_CONN_EVENT_DATA_RECEIVE: + ESP_LOGI(TAG, "ESP_BLE_CONN_EVENT_DATA_RECEIVE\n"); + esp_ble_conn_data_t *conn_data = (esp_ble_conn_data_t *)event_data; + esp_ble_anp_data_t ble_anp_data; + memset(&ble_anp_data, 0, sizeof(esp_ble_anp_data_t)); + switch (conn_data->uuid.uuid16) { + case BLE_ANP_CHR_UUID16_NEW_ALERT: + esp_ble_anp_handle_new_alert(conn_data->data, conn_data->data_len); + memcpy(&ble_anp_data.new_alert_val, ble_svc_ans_new_alert_val, sizeof(ble_svc_ans_new_alert_val)); + esp_event_post(BLE_ANP_EVENTS, BLE_ANP_CHR_UUID16_NEW_ALERT, &ble_anp_data, sizeof(esp_ble_anp_data_t), portMAX_DELAY); + break; + case BLE_ANP_CHR_UUID16_UNR_ALERT_STAT: + esp_ble_anp_handle_unr_alert(conn_data->data, conn_data->data_len); + memcpy(&ble_anp_data.unr_alert_stat, ble_svc_ans_unr_alert_stat, sizeof(ble_svc_ans_unr_alert_stat)); + esp_event_post(BLE_ANP_EVENTS, BLE_ANP_CHR_UUID16_UNR_ALERT_STAT, &ble_anp_data, sizeof(esp_ble_anp_data_t), portMAX_DELAY); + break; + default: + break; + } + + break; + default: + break; + } +} + +esp_err_t esp_ble_anp_get_new_alert(uint8_t cat_id, uint8_t *cat_val) +{ + if (!cat_val) { + return ESP_ERR_INVALID_ARG; + } + + esp_ble_conn_data_t inbuff = { + .type = BLE_CONN_UUID_TYPE_16, + .uuid = { + .uuid16 = BLE_ANP_CHR_UUID16_SUP_NEW_ALERT_CAT, + }, + .data = NULL, + .data_len = 0, + }; + + esp_err_t rc = esp_ble_conn_read(&inbuff); + if (rc) { + ESP_LOGE(TAG, "Read the Value of Supported New Alert Category %s!", esp_err_to_name(rc)); + return rc; + } + + ble_svc_ans_new_alert_cat = *inbuff.data; + + ESP_LOG_BUFFER_HEX_LEVEL(TAG, inbuff.data, inbuff.data_len, ESP_LOG_INFO); + + if (cat_id != 0xFF) { + if (cat_id > BLE_ANP_CAT_NUM) { + ESP_LOGE(TAG, "Invalid Category ID %d to Check Supported New Alert Category!", cat_id); + return ESP_ERR_INVALID_ARG; + } + + // 4.12 Check Supported New Alert Category + *cat_val = ((ble_svc_ans_new_alert_cat >> cat_id) & 0x01); + } else { + // 4.03 Read the Value of Supported New Alert Category + *cat_val = ble_svc_ans_new_alert_cat; + } + + return ESP_OK; +} + +esp_err_t esp_ble_anp_set_new_alert(uint8_t cat_id, esp_ble_anp_option_t option) +{ + esp_err_t rc = ESP_ERR_INVALID_ARG; + if (cat_id > BLE_ANP_CAT_NUM && cat_id != 0xFF) { + ESP_LOGE(TAG, "Invalid Category ID %d to Request or Recovery New Alert!", cat_id); + return ESP_ERR_INVALID_ARG; + } + + switch (option) { + case BLE_ANP_OPT_ENABLE: + if (cat_id != 0xFF) { + ble_svc_ans_new_alert_cnt[cat_id] += 1; + } + rc = esp_ble_anp_alert_ctrl_set(BLE_ANP_CMD_EN_NEW_ALERT_CAT, cat_id); + if (!rc) { + rc = esp_ble_anp_alert_ctrl_set(BLE_ANP_CMD_NOT_NEW_ALERT_IMMEDIATE, cat_id); + } + break; + case BLE_ANP_OPT_DISABLE: + rc = esp_ble_anp_alert_ctrl_set(BLE_ANP_CMD_DIS_NEW_ALERT_CAT, cat_id); + break; + case BLE_ANP_OPT_RECOVER: + rc = esp_ble_anp_alert_ctrl_set(BLE_ANP_CMD_EN_NEW_ALERT_CAT, cat_id); + if (!rc) { + rc = esp_ble_anp_alert_ctrl_set(BLE_ANP_CMD_NOT_NEW_ALERT_IMMEDIATE, 0xFF); + } + break; + default: + break; + } + + return rc; +} + +esp_err_t esp_ble_anp_get_unr_alert(uint8_t cat_id, uint8_t *cat_val) +{ + if (!cat_val) { + return ESP_ERR_INVALID_ARG; + } + + esp_ble_conn_data_t inbuff = { + .type = BLE_CONN_UUID_TYPE_16, + .uuid = { + .uuid16 = BLE_ANP_CHR_UUID16_SUP_UNR_ALERT_CAT, + }, + .data = NULL, + .data_len = 0, + }; + + esp_err_t rc = esp_ble_conn_read(&inbuff); + if (rc) { + ESP_LOGE(TAG, "Read the Value of Supported Unread Alert Category %s!", esp_err_to_name(rc)); + return rc; + } + + ble_svc_ans_unr_alert_cat = *inbuff.data; + + ESP_LOG_BUFFER_HEX_LEVEL(TAG, &ble_svc_ans_unr_alert_cat, sizeof(ble_svc_ans_unr_alert_cat), ESP_LOG_INFO); + + if (cat_id != 0xFF) { + if (cat_id > BLE_ANP_CAT_NUM) { + ESP_LOGE(TAG, "Invalid Category ID %d to Check Supported Unread Alert Status Category!", cat_id); + return ESP_ERR_INVALID_ARG; + } + + /* 4.13 Check Supported Unread Alert Status Category */ + *cat_val = ((ble_svc_ans_unr_alert_cat >> cat_id) & 0x01); + } else { + /* 4.4 Read the Value of Supported Unread Alert Status Category */ + *cat_val = ble_svc_ans_unr_alert_cat; + } + + return ESP_OK; +} + +esp_err_t esp_ble_anp_set_unr_alert(uint8_t cat_id, esp_ble_anp_option_t option) +{ + esp_err_t rc = ESP_ERR_INVALID_ARG; + if (cat_id > BLE_ANP_CAT_NUM && cat_id != 0xFF) { + ESP_LOGE(TAG, "Invalid Category ID %d to Request or Recovery Unread Alert Status!", cat_id); + return ESP_ERR_INVALID_ARG; + } + + switch (option) { + case BLE_ANP_OPT_ENABLE: + if (cat_id != 0xFF) { + ble_svc_ans_unr_alert_cnt[cat_id] += 1; + } + rc = esp_ble_anp_alert_ctrl_set(BLE_ANP_CMD_EN_UNR_ALERT_CAT, cat_id); + if (!rc) { + rc = esp_ble_anp_alert_ctrl_set(BLE_ANP_CMD_NOT_UNR_ALERT_IMMEDIATE, cat_id); + } + break; + case BLE_ANP_OPT_DISABLE: + rc = esp_ble_anp_alert_ctrl_set(BLE_ANP_CMD_DIS_UNR_ALERT_CAT, cat_id); + break; + case BLE_ANP_OPT_RECOVER: + rc = esp_ble_anp_alert_ctrl_set(BLE_ANP_CMD_EN_UNR_ALERT_CAT, cat_id); + if (!rc) { + rc = esp_ble_anp_alert_ctrl_set(BLE_ANP_CMD_NOT_UNR_ALERT_IMMEDIATE, 0xFF); + } + break; + default: + break; + } + + return rc; +} + +esp_err_t esp_ble_anp_init(void) +{ + return esp_event_handler_register(BLE_CONN_MGR_EVENTS, ESP_EVENT_ANY_ID, esp_ble_anp_event, NULL); +} + +esp_err_t esp_ble_anp_deinit(void) +{ + return esp_event_handler_unregister(BLE_CONN_MGR_EVENTS, ESP_EVENT_ANY_ID, esp_ble_anp_event); +} diff --git a/examples/.build-rules.yml b/examples/.build-rules.yml index 851ca1ee1..15cd8c68b 100644 --- a/examples/.build-rules.yml +++ b/examples/.build-rules.yml @@ -23,6 +23,13 @@ examples/bluetooth/ble_conn_mgr/ble_spp/spp_server: enable: - if: SOC_BLE_SUPPORTED == 1 +examples/bluetooth/ble_profiles/ble_anp: + enable: + - if: IDF_TARGET in ["esp32","esp32c3"] and (IDF_VERSION_MAJOR == 4 and IDF_VERSION_MINOR == 3) + - if: IDF_TARGET in ["esp32","esp32s3","esp32c3"] and (IDF_VERSION_MAJOR == 4 and IDF_VERSION_MINOR == 4) + - if: IDF_TARGET in ["esp32","esp32s3","esp32c2","esp32c3","esp32h2"] and (IDF_VERSION_MAJOR == 5 and IDF_VERSION_MINOR == 0) + - if: IDF_TARGET in ["esp32","esp32s3","esp32c2","esp32c3","esp32c6","esp32h2"] and (IDF_VERSION_MAJOR == 5 and IDF_VERSION_MINOR == 1) + examples/bluetooth/ble_profiles/ble_hrp: enable: - if: IDF_TARGET in ["esp32","esp32c3"] and (IDF_VERSION_MAJOR == 4 and IDF_VERSION_MINOR == 3) diff --git a/examples/bluetooth/ble_profiles/ble_anp/CMakeLists.txt b/examples/bluetooth/ble_profiles/ble_anp/CMakeLists.txt new file mode 100644 index 000000000..b6e3d61c9 --- /dev/null +++ b/examples/bluetooth/ble_profiles/ble_anp/CMakeLists.txt @@ -0,0 +1,6 @@ +# 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.5) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(ble_anp) diff --git a/examples/bluetooth/ble_profiles/ble_anp/README.md b/examples/bluetooth/ble_profiles/ble_anp/README.md new file mode 100644 index 000000000..ccbf12f38 --- /dev/null +++ b/examples/bluetooth/ble_profiles/ble_anp/README.md @@ -0,0 +1,228 @@ +| Supported Targets | ESP32 | ESP32-C3 | ESP32-C2 | ESP32-S3 | +| ----------------- | ----- | -------- | -------- | -------- | + +# BLE Alert Notification Profile Example + +(See the README.md file in the upper level 'examples' directory for more information about examples.) + +This example creates GATT client and performs passive scan, it then connects to peripheral device if the device advertises connectability and the device advertises support for the Alert Notification service (0x1811) as primary service UUID. + +At the same time, it also creates an interactive shell and then controlled/interacted with over a serial interface to emulation the ANP behavior. + +It performs three GATT operations against the specified peer: + +* Reads the ANS Supported New Alert Category characteristic. +* After the read operation is completed, writes the ANS Alert Notification Control Point characteristic. +* After the write operation is completed, subscribes to notifications for the ANS Unread Alert Status characteristic. + +If the peer does not support a required service, characteristic, or descriptor, then the peer lied when it claimed support for the alert notification service! When this happens, or if a GATT procedure fails, this function immediately terminates the connection. + +It uses Bluetooth controller based on BLE connection management. + +This example aims at understanding alert notification profile's characteristic operations and BLE connection management APIs + +To test this demo, use any BLE GATT server app that advertises support for the Alert Notification service (0x1811) and includes it in the GATT database. + +## Using Examples + +### Functions Processing + +Use the commands as follows can check the alert notification profile functions. + +* anp + + * `-t (Mandatory)`: Get or Set function + * `-c (Mandatory)`: Category ID + * `-o (Mandatory)`: Enable, Disable and Recovery optional + +* New Alert + + * Read the Value of Supported New Alert Category +``` +anp -t 1 -c 1 -o 0 +anp -t 1 -c 255 -o 0 +``` + + * Enable the specific category for New Alert to Notify when New Alert Count Changes +``` +anp -t 2 -c 1 -o 0 +``` + + * Disable the specific category for New Alert to Notify when New Alert Count Changes +``` +anp -t 2 -c 1 -o 1 +``` + + * Recovery from Connection Loss for New Alerts +``` +anp -t 2 -c 1 -o 2 +``` + +* Unread Alert + + * Read the Value of Supported Unread Alert Category +``` +anp -t 3 -c 1 -o 0 +anp -t 3 -c 255 -o 0 +``` + + * Enable the specific category for Unread Alert to Notify when Unread Alert Status Changes +``` +anp -t 4 -c 1 -o 0 +``` + + * Disable the specific category for Unread Alert to Notify when Unread Alert Status Changes +``` +anp -t 4 -c 1 -o 1 +``` + + * Recovery from Connection Loss for Unread Alert +``` +anp -t 4 -c 1 -o 2 +``` + +## How to Use Example + +Before project configuration and build, be sure to set the correct chip target using: + +```bash +idf.py set-target +``` + +### Hardware Required + +* A development board with ESP32/ESP32-C3 SoC (e.g., ESP32-DevKitC, ESP-WROVER-KIT, etc.) +* A USB cable for Power supply and programming + +See [Development Boards](https://www.espressif.com/en/products/devkits) for more information about it. + +### Configure the Project + +Open the project configuration menu: + +```bash +idf.py menuconfig +``` + +In the `Example Configuration` menu: + +* Select advertisement name of device from `Example Configuration --> Advertisement name`, default is `BLE_ANS`. + +### Build and Flash + +Run `idf.py -p PORT flash monitor` to build, flash and monitor the project. + +(To exit the serial monitor, type ``Ctrl-]``.) + +See the [Getting Started Guide](https://idf.espressif.com/) for full steps to configure and use ESP-IDF to build projects. + +## Example Output + +This is the console output on successful connection: + +``` +This is an example of profile component. +Type 'help' to get the list of commands. +Use UP/DOWN arrows to navigate through command history. +Press TAB when typing command name to auto-complete. +Press Enter or Ctrl+C will terminate the console environment. +I (348) BTDM_INIT: BT controller compile version [80abacd] +I (368) phy_init: phy_version 950,11a46e9,Oct 21 2022,08:56:12 +I (418) system_api: Base MAC address is not set +I (418) system_api: read default base MAC address from EFUSE +I (418) BTDM_INIT: Bluetooth MAC: 84:f7:03:08:21:1a + +I (428) esp_nimble: BLE Host Task Started +I (428) NimBLE: GAP procedure initiated: stop advertising. + +I (438) NimBLE: GAP procedure initiated: discovery; +I (448) NimBLE: own_addr_type=0 filter_policy=0 passive=1 limited=0 filter_duplicates=1 +I (458) NimBLE: duration=forever +I (458) NimBLE: + +I (468) NimBLE: GAP procedure initiated: connect; +I (468) NimBLE: peer_addr_type=0 peer_addr= +I (468) NimBLE: 84:f7:03:09:09:ca +I (478) NimBLE: scan_itvl=18 scan_window=17 itvl_min=25 itvl_max=26 latency=1 supervision_timeout=20 min_ce_len=3 max_ce_len=4 own_addr_type=0 +I (488) NimBLE: + +esp32c3> I (638) NimBLE: GATT procedure initiated: discover all services + +I (638) app_main: ESP_BLE_CONN_EVENT_CONNECTED +I (698) NimBLE: GATT procedure initiated: discover all characteristics; +I (698) NimBLE: start_handle=1 end_handle=5 + +I (858) NimBLE: GATT procedure initiated: discover all characteristics; +I (858) NimBLE: start_handle=6 end_handle=9 + +I (1048) NimBLE: GATT procedure initiated: discover all characteristics; +I (1058) NimBLE: start_handle=10 end_handle=65535 + +I (1348) NimBLE: GATT procedure initiated: discover all descriptors; +I (1348) NimBLE: chr_val_handle=8 end_handle=9 + +I (1438) NimBLE: GATT procedure initiated: discover all descriptors; +I (1448) NimBLE: chr_val_handle=12 end_handle=13 + +I (1538) NimBLE: GATT procedure initiated: discover all descriptors; +I (1538) NimBLE: chr_val_handle=15 end_handle=17 + +I (1638) NimBLE: GATT procedure initiated: discover all descriptors; +I (1638) NimBLE: chr_val_handle=19 end_handle=20 + +I (1738) NimBLE: GATT procedure initiated: discover all descriptors; +I (1738) NimBLE: chr_val_handle=22 end_handle=24 + +I (1828) NimBLE: GATT procedure initiated: discover all descriptors; +I (1838) NimBLE: chr_val_handle=26 end_handle=65535 + +I (2028) esp_nimble: Service discovery complete; rc=0, conn_handle=1 +I (2028) app_main: ESP_BLE_CONN_EVENT_DISC_COMPLETE +esp32c3> +esp32c3> help +help + Print the list of registered commands + +anp [-t <01~04>] [-c <01~255>] [-o <00~02>] + Alert Notification Client + -t, --type=<01~04> 01 Get supported new alert category + 02 Set supported new alert category + 03 Get supported unread alert status category + 04 Set supported unread alert status category + -c, --category=<01~255> Category ID + -o, --option=<00~02> 0: Enable, 1: Disable, 2: Recover + +esp32c3> anp -t 2 -c 3 -o 0 +I (27848) NimBLE: GATT procedure initiated: write; +I (27848) NimBLE: att_handle=26 len=2 + +I (27928) esp_anp: Configure Alert Notification Control Point Success! +I (27928) esp_anp: 00 03 +I (27928) esp_anp: Request the New Alert in the server to notify immediately on current enabled categories 3 +I (27938) NimBLE: GATT procedure initiated: write; +I (27958) NimBLE: att_handle=26 len=2 + +I (28028) esp_nimble: received notification; conn_handle=1 attr_handle=15 attr_len=2 + +I (28028) app_main: ESP_BLE_CONN_EVENT_DATA_RECEIVE + +I (28038) esp_anp: ESP_BLE_CONN_EVENT_DATA_RECEIVE + +I (28038) esp_anp: Receive Notification of New Alert on cat_id 3 which count change to 0 +I (28048) app_anp: Get the current message counts 3 from category 0 of supported new alert +I (28028) esp_anp: Configure Alert Notification Control Point Success! +I (28068) esp_anp: 04 03 +I (28068) app_anp: 4.06 Request Category 3 of Supported New Alert Category to Notify +esp32c3> I (43338) esp_nimble: received notification; conn_handle=1 attr_handle=15 attr_len=2 + +I (43338) app_main: ESP_BLE_CONN_EVENT_DATA_RECEIVE + +I (43348) esp_anp: ESP_BLE_CONN_EVENT_DATA_RECEIVE + +I (43348) esp_anp: Receive Notification of New Alert on cat_id 3 which count change to 1 +I (43358) app_anp: Get the current message counts 3 from category 1 of supported new alert +``` + +## Troubleshooting + +For any technical queries, please open an [issue](https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you soon. diff --git a/examples/bluetooth/ble_profiles/ble_anp/main/CMakeLists.txt b/examples/bluetooth/ble_profiles/ble_anp/main/CMakeLists.txt new file mode 100644 index 000000000..224607cfe --- /dev/null +++ b/examples/bluetooth/ble_profiles/ble_anp/main/CMakeLists.txt @@ -0,0 +1 @@ +idf_component_register(SRCS "app_main.c" "app_anp.c" "app_console.c") diff --git a/examples/bluetooth/ble_profiles/ble_anp/main/Kconfig.projbuild b/examples/bluetooth/ble_profiles/ble_anp/main/Kconfig.projbuild new file mode 100644 index 000000000..9cd08c8b9 --- /dev/null +++ b/examples/bluetooth/ble_profiles/ble_anp/main/Kconfig.projbuild @@ -0,0 +1,19 @@ +menu "Example Configuration" + config EXAMPLE_BLE_ADV_NAME + string "Advertisement name" + default "BLE_ANS" + help + The device name inside advertisement. + + config EXAMPLE_BLE_SUB_ADV + string "Subsequent advertisement data" + default "SUB_ADV" + help + The data inside subsequent advertisements. + + config EXAMPLE_BLE_SUB_SCAN + string "Subsequent scan responses data" + default "SUB_SCAN" + help + The data inside subsequent scan responses. +endmenu diff --git a/examples/bluetooth/ble_profiles/ble_anp/main/app_anp.c b/examples/bluetooth/ble_profiles/ble_anp/main/app_anp.c new file mode 100644 index 000000000..160c8855a --- /dev/null +++ b/examples/bluetooth/ble_profiles/ble_anp/main/app_anp.c @@ -0,0 +1,185 @@ +/* + * SPDX-FileCopyrightText: 2019-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include "esp_system.h" +#include "esp_log.h" +#include "esp_console.h" +#include "argtable3/argtable3.h" + +#include "esp_anp.h" + +static const char* TAG = "app_anp"; + +static esp_err_t app_anp_get_new_alert(uint8_t cat_id) +{ + uint8_t cat_val; + + esp_err_t rc = esp_ble_anp_get_new_alert(cat_id, &cat_val); + if (rc) { + return rc; + } + + if (cat_id != 0xFF) { + ESP_LOGI(TAG, "4.12 Category %d of Supported New Alert Category is %s", cat_id, cat_val ? "Enabled" : "Disabled"); + } else { + ESP_LOGI(TAG, "4.03 The Value of Supported New Alert Category is 0x%0x", cat_val); + } + + return ESP_OK; +} + +static esp_err_t app_anp_set_new_alert(uint8_t cat_id, uint8_t option) +{ + esp_err_t rc = esp_ble_anp_set_new_alert(cat_id, option); + if (rc) { + return rc; + } + + if (cat_id != 0xFF) { + ESP_LOGI(TAG, "4.06 Request Category %d of Supported New Alert Category to Notify", cat_id); + } else { + ESP_LOGI(TAG, "4.10 Recover All Category of Supported New Alert Category to Notify"); + } + + return ESP_OK; +} + +static esp_err_t app_anp_get_unr_alert(uint8_t cat_id) +{ + uint8_t cat_val; + + esp_err_t rc = esp_ble_anp_get_unr_alert(cat_id, &cat_val); + if (rc) { + return rc; + } + + if (cat_id != 0xFF) { + ESP_LOGI(TAG, "4.13 Category %d of Supported Unread Alert Status Category is %s", cat_id, cat_val ? "Enabled" : "Disabled"); + } else { + ESP_LOGI(TAG, "4.04 The Value of Supported Unread Alert Status Category is 0x%0x", cat_val); + } + + return ESP_OK; +} + +static esp_err_t app_anp_set_unr_alert(uint8_t cat_id, uint8_t option) +{ + esp_err_t rc = esp_ble_anp_set_unr_alert(cat_id, option); + if (rc) { + return rc; + } + + if (cat_id != 0xFF) { + ESP_LOGI(TAG, "4.08 Request Category %d of Supported Unread Alert Status Category to Notify", cat_id); + } else { + ESP_LOGI(TAG, "4.11 Recover All Category of Supported Unread Alert Status Category to Notify"); + } + + return ESP_OK; +} + +static esp_err_t app_anp_run(uint8_t type, uint8_t cat_id, uint8_t option) +{ + esp_err_t rc = ESP_FAIL; + + switch (type) { + case 1: + rc = app_anp_get_new_alert(cat_id); + break; + case 2: + rc = app_anp_set_new_alert(cat_id, option); + break; + case 3: + rc = app_anp_get_unr_alert(cat_id); + break; + case 4: + rc = app_anp_set_unr_alert(cat_id, option); + break; + default: + break; + } + + if (rc) { + ESP_LOGE(TAG, "Invalid option"); + } + + return rc; +} + +static struct { + struct arg_int *type; + struct arg_int *category; + struct arg_int *option; + struct arg_end *end; +} anp_args; + +static int app_anp_func(int argc, char **argv) +{ + int nerrors = arg_parse(argc, argv, (void **) &anp_args); + if (nerrors != 0) { + arg_print_errors(stderr, anp_args.end, argv[0]); + return 1; + } + + if (!anp_args.type->count) { + return 1; + }; + + if (!anp_args.category->count) { + return 1; + } + + if (!anp_args.option->count) { + return 1; + } + + return app_anp_run(anp_args.type->ival[0], anp_args.category->ival[0], anp_args.option->ival[0]); +} + +static void app_anp_event_handler(void *handler_args, esp_event_base_t base, int32_t id, void *event_data) +{ + if (base != BLE_ANP_EVENTS) { + return; + } + + esp_ble_anp_data_t *ble_anp_data = (esp_ble_anp_data_t *)event_data; + switch (id) { + case BLE_ANP_CHR_UUID16_NEW_ALERT: + ESP_LOGI(TAG, "Get the current message counts %d from category %d of supported new alert", ble_anp_data->unr_alert_stat.cat_id, ble_anp_data->unr_alert_stat.count); + break; + case BLE_ANP_CHR_UUID16_UNR_ALERT_STAT: + ESP_LOGI(TAG, "Get the current message counts %d from category %d of supported unread alert status", ble_anp_data->unr_alert_stat.cat_id, ble_anp_data->unr_alert_stat.count); + break; + default: + break; + } +} + +void register_anp(void) +{ + anp_args.type = arg_int0("t", "type", "<01~04>", "01 Get supported new alert category\n \ + 02 Set supported new alert category\n \ + 03 Get supported unread alert status category\n \ + 04 Set supported unread alert status category"); + anp_args.category = arg_int0("c", "category", "<01~255>", "Category ID"); + anp_args.option = arg_int0("o", "option", "<00~02>", "0: Enable, 1: Disable, 2: Recover"); + anp_args.end = arg_end(2); + + const esp_console_cmd_t join_cmd = { + .command = "anp", + .help = "Alert Notification Profile", + .hint = NULL, + .func = &app_anp_func, + .argtable = &anp_args + }; + + ESP_ERROR_CHECK(esp_console_cmd_register(&join_cmd)); + + esp_ble_anp_init(); + esp_event_handler_register(BLE_ANP_EVENTS, ESP_EVENT_ANY_ID, app_anp_event_handler, NULL); +} diff --git a/examples/bluetooth/ble_profiles/ble_anp/main/app_anp.h b/examples/bluetooth/ble_profiles/ble_anp/main/app_anp.h new file mode 100644 index 000000000..26755167b --- /dev/null +++ b/examples/bluetooth/ble_profiles/ble_anp/main/app_anp.h @@ -0,0 +1,18 @@ +/* + * SPDX-FileCopyrightText: 2019-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +int app_console_init(void); +void register_anp(void); + +#ifdef __cplusplus +} +#endif diff --git a/examples/bluetooth/ble_profiles/ble_anp/main/app_console.c b/examples/bluetooth/ble_profiles/ble_anp/main/app_console.c new file mode 100644 index 000000000..6f18d849a --- /dev/null +++ b/examples/bluetooth/ble_profiles/ble_anp/main/app_console.c @@ -0,0 +1,164 @@ +/* + * SPDX-FileCopyrightText: 2019-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include "esp_system.h" +#include "esp_log.h" +#include "esp_console.h" +#include "esp_vfs_dev.h" +#include "driver/uart.h" +#include "linenoise/linenoise.h" +#include "argtable3/argtable3.h" +#include "esp_vfs_fat.h" + +static const char* TAG = "app_console"; + +#define PROMPT_STR CONFIG_IDF_TARGET + +static void initialize_console(void) +{ + /* Drain stdout before reconfiguring it */ + fflush(stdout); + fsync(fileno(stdout)); + + /* Disable buffering on stdin */ + setvbuf(stdin, NULL, _IONBF, 0); + + /* Minicom, screen, idf_monitor send CR when ENTER key is pressed */ + esp_vfs_dev_uart_port_set_rx_line_endings(CONFIG_ESP_CONSOLE_UART_NUM, ESP_LINE_ENDINGS_CR); + /* Move the caret to the beginning of the next line on '\n' */ + esp_vfs_dev_uart_port_set_tx_line_endings(CONFIG_ESP_CONSOLE_UART_NUM, ESP_LINE_ENDINGS_CRLF); + + /* Configure UART. Note that REF_TICK is used so that the baud rate remains + * correct while APB frequency is changing in light sleep mode. + */ + const uart_config_t uart_config = { + .baud_rate = CONFIG_ESP_CONSOLE_UART_BAUDRATE, + .data_bits = UART_DATA_8_BITS, + .parity = UART_PARITY_DISABLE, + .stop_bits = UART_STOP_BITS_1, +#if SOC_UART_SUPPORT_REF_TICK + .source_clk = UART_SCLK_REF_TICK, +#elif SOC_UART_SUPPORT_XTAL_CLK + .source_clk = UART_SCLK_XTAL, +#endif + }; + /* Install UART driver for interrupt-driven reads and writes */ + ESP_ERROR_CHECK(uart_driver_install(CONFIG_ESP_CONSOLE_UART_NUM, 256, 0, 0, NULL, 0)); + ESP_ERROR_CHECK(uart_param_config(CONFIG_ESP_CONSOLE_UART_NUM, &uart_config)); + + /* Tell VFS to use UART driver */ + esp_vfs_dev_uart_use_driver(CONFIG_ESP_CONSOLE_UART_NUM); + + /* Initialize the console */ + esp_console_config_t console_config = { + .max_cmdline_args = 8, + .max_cmdline_length = 256, +#if CONFIG_LOG_COLORS + .hint_color = atoi(LOG_COLOR_CYAN) +#endif + }; + ESP_ERROR_CHECK(esp_console_init(&console_config)); + + /* Configure linenoise line completion library */ + /* Enable multiline editing. If not set, long commands will scroll within + * single line. + */ + linenoiseSetMultiLine(1); + + /* Tell linenoise where to get command completions and hints */ + linenoiseSetCompletionCallback(&esp_console_get_completion); + linenoiseSetHintsCallback((linenoiseHintsCallback*) &esp_console_get_hint); + + /* Set command history size */ + linenoiseHistorySetMaxLen(100); + +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 4, 0) + /* Set command maximum length */ + linenoiseSetMaxLineLen(console_config.max_cmdline_length); +#endif + + /* Don't return empty lines */ + linenoiseAllowEmpty(false); + +} + +static void app_console_task(void *arg) +{ + initialize_console(); + + /* Register commands */ + esp_console_register_help_command(); + + /* Prompt to be printed before each line. + * This can be customized, made dynamic, etc. + */ + const char* prompt = LOG_COLOR_I PROMPT_STR "> " LOG_RESET_COLOR; + + printf("\n" + "This is an example of profile component.\n" + "Type 'help' to get the list of commands.\n" + "Use UP/DOWN arrows to navigate through command history.\n" + "Press TAB when typing command name to auto-complete.\n" + "Press Enter or Ctrl+C will terminate the console environment.\n"); + + /* Figure out if the terminal supports escape sequences */ + int probe_status = linenoiseProbe(); + if (probe_status) { /* zero indicates success */ + printf("\n" + "Your terminal application does not support escape sequences.\n" + "Line editing and history features are disabled.\n" + "On Windows, try using Putty instead.\n"); + linenoiseSetDumbMode(1); +#if CONFIG_LOG_COLORS + /* Since the terminal doesn't support escape sequences, + * don't use color codes in the prompt. + */ + prompt = PROMPT_STR "> "; +#endif //CONFIG_LOG_COLORS + } + + /* Main loop */ + while (true) { + /* Get a line using linenoise. + * The line is returned when ENTER is pressed. + */ + char* line = linenoise(prompt); + if (line == NULL) { /* Break on EOF or error */ + continue; + } + /* Add the command to the history if not empty*/ + if (strlen(line) > 0) { + linenoiseHistoryAdd(line); + } + + /* Try to run the command */ + int ret; + esp_err_t err = esp_console_run(line, &ret); + if (err == ESP_ERR_NOT_FOUND) { + printf("Unrecognized command\n"); + } else if (err == ESP_ERR_INVALID_ARG) { + // command was empty + } else if (err == ESP_OK && ret != ESP_OK) { + printf("Command returned non-zero error code: 0x%x (%s)\n", ret, esp_err_to_name(ret)); + } else if (err != ESP_OK) { + printf("Internal error: %s\n", esp_err_to_name(err)); + } + /* linenoise allocates line buffer on the heap, so need to free it */ + linenoiseFree(line); + } + + ESP_LOGE(TAG, "Error or end-of-input, terminating console"); + esp_console_deinit(); + + vTaskDelete(NULL); +} + +int app_console_init(void) +{ + return xTaskCreate(app_console_task, "app_console", 4096, (void *) 0, 3, NULL); +} diff --git a/examples/bluetooth/ble_profiles/ble_anp/main/app_main.c b/examples/bluetooth/ble_profiles/ble_anp/main/app_main.c new file mode 100644 index 000000000..a5c662aec --- /dev/null +++ b/examples/bluetooth/ble_profiles/ble_anp/main/app_main.c @@ -0,0 +1,71 @@ +/* + * SPDX-FileCopyrightText: 2019-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include "esp_log.h" +#include "nvs_flash.h" + +#include "esp_ble_conn_mgr.h" +#include "app_anp.h" + +static const char *TAG = "app_main"; + +static void app_ble_conn_event_handler(void *handler_args, esp_event_base_t base, int32_t id, void *event_data) +{ + if (base != BLE_CONN_MGR_EVENTS) { + return; + } + + switch (id) { + case ESP_BLE_CONN_EVENT_CONNECTED: + ESP_LOGI(TAG, "ESP_BLE_CONN_EVENT_CONNECTED"); + break; + case ESP_BLE_CONN_EVENT_DISCONNECTED: + ESP_LOGI(TAG, "ESP_BLE_CONN_EVENT_DISCONNECTED"); + break; + case ESP_BLE_CONN_EVENT_DISC_COMPLETE: + ESP_LOGI(TAG, "ESP_BLE_CONN_EVENT_DISC_COMPLETE"); + break; + case ESP_BLE_CONN_EVENT_DATA_RECEIVE: + ESP_LOGI(TAG, "ESP_BLE_CONN_EVENT_DATA_RECEIVE\n"); + break; + default: + break; + } +} + +void +app_main(void) +{ + esp_ble_conn_config_t config = { + .device_name = CONFIG_EXAMPLE_BLE_ADV_NAME, + .broadcast_data = CONFIG_EXAMPLE_BLE_SUB_ADV + }; + + esp_err_t ret; + + // Initialize NVS + ret = nvs_flash_init(); + if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { + ESP_ERROR_CHECK(nvs_flash_erase()); + ret = nvs_flash_init(); + } + ESP_ERROR_CHECK(ret); + + esp_event_loop_create_default(); + esp_event_handler_register(BLE_CONN_MGR_EVENTS, ESP_EVENT_ANY_ID, app_ble_conn_event_handler, NULL); + + app_console_init(); + register_anp(); + + esp_ble_conn_init(&config); + if (esp_ble_conn_start() != ESP_OK) { + esp_ble_conn_stop(); + esp_ble_conn_deinit(); + esp_event_handler_unregister(BLE_CONN_MGR_EVENTS, ESP_EVENT_ANY_ID, app_ble_conn_event_handler); + } +} diff --git a/examples/bluetooth/ble_profiles/ble_anp/main/idf_component.yml b/examples/bluetooth/ble_profiles/ble_anp/main/idf_component.yml new file mode 100644 index 000000000..63159e0dc --- /dev/null +++ b/examples/bluetooth/ble_profiles/ble_anp/main/idf_component.yml @@ -0,0 +1,8 @@ +dependencies: + idf: ">=4.3" + ble_conn_mgr: + version: "~0.1.0" + override_path: "../../../../../components/bluetooth/ble_conn_mgr" + ble_anp: + version: "~0.1.0" + override_path: "../../../../../components/bluetooth/ble_profiles/std/ble_anp" diff --git a/examples/bluetooth/ble_profiles/ble_anp/sdkconfig.ci.nimble b/examples/bluetooth/ble_profiles/ble_anp/sdkconfig.ci.nimble new file mode 100644 index 000000000..f49256fb2 --- /dev/null +++ b/examples/bluetooth/ble_profiles/ble_anp/sdkconfig.ci.nimble @@ -0,0 +1 @@ +CONFIG_BT_NIMBLE_ENABLED=y diff --git a/examples/bluetooth/ble_profiles/ble_anp/sdkconfig.defaults b/examples/bluetooth/ble_profiles/ble_anp/sdkconfig.defaults new file mode 100644 index 000000000..e5bb4090c --- /dev/null +++ b/examples/bluetooth/ble_profiles/ble_anp/sdkconfig.defaults @@ -0,0 +1,7 @@ +# Override some defaults so BT stack is enabled +# by default in this example +CONFIG_BT_ENABLED=y +CONFIG_BT_NIMBLE_ENABLED=y + +CONFIG_BLE_CONN_MGR_ROLE_CENTRAL=y +CONFIG_BLE_ALERT_NOTIFICATION_PROFILES=y