fix(nimble): Added support for ANCS in nimble

This commit is contained in:
Astha Verma
2025-05-29 18:02:06 +05:30
parent 4ce1da7569
commit a045046a7a
10 changed files with 1210 additions and 0 deletions

View File

@ -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(ble_ancs)

View File

@ -0,0 +1,65 @@
| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C5 | ESP32-C6 | ESP32-H2 | ESP32-S3 |
| ----------------- | ----- | -------- | -------- | -------- | -------- | -------- | -------- |
# ESP-IDF NimBLE ANCS Example
The purpose of the Apple Notification Center Service (ANCS) is to give Bluetooth accessories (that connect to iOS devices through a Bluetooth low-energy link) a simple and convenient way to access many kinds of notifications that are generated on iOS devices.
## How to Use Example
Before project configuration and build, be sure to set the correct chip target using:
```bash
idf.py set-target <chip_name>
```
The Apple Notification Center Service is a primary service whose service UUID is:
`7905F431-B5CE-4E99-A40F-4B1E122D00D0`
Only one instance of the ANCS may be present on an NP. Due to the nature of iOS, the ANCS is not guaranteed to always be present. As a result, the NC should look for and subscribe to the Service Changed characteristic of the GATT service in order to monitor for the potential publishing and unpublishing of the ANCS at any time.
In its basic form, the ANCS exposes three characteristics:
Notification Source: UUID `9FBF120D-6301-42D9-8C58-25E699A21DBD` (notifiable)
Control Point: UUID `69D1D8F3-45E1-49A8-9821-9BBDFDAAD9D9` (writeable with response)
Data Source: UUID `22EAC6E9-24D6-4BB5-BE44-B36ACE7C7BFB` (notifiable)
All these characteristics require authorization for access.
### Hardware Required
* A development board with ESP32/ESP32-C3/ESP32-H2/ESP32-C2/ESP32-S3 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.
### 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
```
I (466) NimBLE ANCS: BLE Host Task Started
I (466) NimBLE: GAP procedure initiated: stop advertising.
I (476) NimBLE: Device Address:
I (476) NimBLE: 48:27:e2:e9:0e:a6
I (476) NimBLE:
I (476) NimBLE: GAP procedure initiated: advertise;
I (476) NimBLE: disc_mode=2
I (476) NimBLE: adv_channel_map=0 own_addr_type=0 adv_filter_policy=0 adv_itvl_min=0 adv_itvl_max=0
I (486) NimBLE:
I (496) uart: queue free spaces: 8
I (496) main_task: Returned from app_main()
```
## 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.

View File

@ -0,0 +1,5 @@
set(srcs "main.c"
"ble_ancs.c")
idf_component_register(SRCS "${srcs}"
INCLUDE_DIRS ".")

View File

@ -0,0 +1,14 @@
menu "Example Configuration"
config EXAMPLE_EXTENDED_ADV
bool
depends on SOC_BLE_50_SUPPORTED && BT_NIMBLE_50_FEATURE_SUPPORT
default y if SOC_ESP_NIMBLE_CONTROLLER
select BT_NIMBLE_EXT_ADV
prompt "Enable Extended Adv"
help
Use this option to enable extended advertising in the example.
If this option is disabled, ensure config BT_NIMBLE_EXT_ADV is
also disabled from Nimble stack menuconfig
endmenu

View File

@ -0,0 +1,212 @@
/*
* SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <stdlib.h>
#include <string.h>
#include <inttypes.h>
#include "esp_log.h"
#include "ble_ancs.h"
#define NimBLE_ANCS_TAG "NimBLE_ANCS"
/*
| EventID(1 Byte) | EventFlags(1 Byte) | CategoryID(1 Byte) | CategoryCount(1 Byte) | NotificationUID(4 Bytes) |
A GATT notification delivered through the Notification Source characteristic contains the following information:
* EventID: This field informs the accessory whether the given iOS notification was added, modified, or removed. The enumerated values for this field are defined
in EventID Values.
* EventFlags: A bitmask whose set bits inform an NC of specificities with the iOS notification. For example, if an iOS notification is considered “important”,
the NC may want to display a more aggressive user interface (UI) to make sure the user is properly alerted. The enumerated bits for this field
are defined in EventFlags.
* CategoryID: A numerical value providing a category in which the iOS notification can be classified. The NP will make a best effort to provide an accurate category
for each iOS notification. The enumerated values for this field are defined in CategoryID Values.
* CategoryCount: The current number of active iOS notifications in the given category. For example, if two unread emails are sitting in a users email inbox, and a new
email is pushed to the users iOS device, the value of CategoryCount is 3.
* NotificationUID: A 32-bit numerical value that is the unique identifier (UID) for the iOS notification. This value can be used as a handle in commands sent to the
Control Point characteristic to interact with the iOS notification.
*/
char *EventID_to_String(uint8_t EventID)
{
char *str = NULL;
switch (EventID)
{
case EventIDNotificationAdded:
str = "New message";
break;
case EventIDNotificationModified:
str = "Modified message";
break;
case EventIDNotificationRemoved:
str = "Removed message";
break;
default:
str = "unknown EventID";
break;
}
return str;
}
char *CategoryID_to_String(uint8_t CategoryID)
{
char *Cidstr = NULL;
switch(CategoryID) {
case CategoryIDOther:
Cidstr = "Other";
break;
case CategoryIDIncomingCall:
Cidstr = "IncomingCall";
break;
case CategoryIDMissedCall:
Cidstr = "MissedCall";
break;
case CategoryIDVoicemail:
Cidstr = "Voicemail";
break;
case CategoryIDSocial:
Cidstr = "Social";
break;
case CategoryIDSchedule:
Cidstr = "Schedule";
break;
case CategoryIDEmail:
Cidstr = "Email";
break;
case CategoryIDNews:
Cidstr = "News";
break;
case CategoryIDHealthAndFitness:
Cidstr = "HealthAndFitness";
break;
case CategoryIDBusinessAndFinance:
Cidstr = "BusinessAndFinance";
break;
case CategoryIDLocation:
Cidstr = "Location";
break;
case CategoryIDEntertainment:
Cidstr = "Entertainment";
break;
default:
Cidstr = "Unknown CategoryID";
break;
}
return Cidstr;
}
/*
| EventID(1 Byte) | EventFlags(1 Byte) | CategoryID(1 Byte) | CategoryCount(1 Byte) | NotificationUID(4 Bytes) |
*/
void ble_receive_apple_notification_source(uint8_t *message, uint16_t message_len)
{
if (!message || message_len < 5) {
return;
}
uint8_t EventID = message[0];
char *EventIDS = EventID_to_String(EventID);
uint8_t EventFlags = message[1];
uint8_t CategoryID = message[2];
char *Cidstr = CategoryID_to_String(CategoryID);
uint8_t CategoryCount = message[3];
uint32_t NotificationUID = (message[4]) | (message[5]<< 8) | (message[6]<< 16) | (message[7] << 24);
ESP_LOGI(NimBLE_ANCS_TAG, "EventID:%s EventFlags:0x%x CategoryID:%s CategoryCount:%d NotificationUID:%" PRIu32, EventIDS, EventFlags, Cidstr, CategoryCount, NotificationUID);
}
void ble_receive_apple_data_source(uint8_t *message, uint16_t message_len)
{
if (!message || message_len == 0) {
return;
}
uint8_t Command_id = message[0];
switch (Command_id)
{
case CommandIDGetNotificationAttributes: {
uint32_t NotificationUID = (message[1]) | (message[2]<< 8) | (message[3]<< 16) | (message[4] << 24);
uint32_t remian_attr_len = message_len - 5;
uint8_t *attrs = &message[5];
ESP_LOGI(NimBLE_ANCS_TAG, "recevice Notification Attributes response Command_id %d NotificationUID %" PRIu32, Command_id, NotificationUID);
while(remian_attr_len > 0) {
uint8_t AttributeID = attrs[0];
uint16_t len = attrs[1] | (attrs[2] << 8);
if(len > (remian_attr_len -3)) {
ESP_LOGE(NimBLE_ANCS_TAG, "data error");
break;
}
switch (AttributeID)
{
case NotificationAttributeIDAppIdentifier:
ESP_LOG_BUFFER_CHAR("Identifier", &attrs[3], len);
break;
case NotificationAttributeIDTitle:
ESP_LOG_BUFFER_CHAR("Title", &attrs[3], len);
break;
case NotificationAttributeIDSubtitle:
ESP_LOG_BUFFER_CHAR("Subtitle", &attrs[3], len);
break;
case NotificationAttributeIDMessage:
ESP_LOG_BUFFER_CHAR("Message", &attrs[3], len);
break;
case NotificationAttributeIDMessageSize:
ESP_LOG_BUFFER_CHAR("MessageSize", &attrs[3], len);
break;
case NotificationAttributeIDDate:
//yyyyMMdd'T'HHmmSS
ESP_LOG_BUFFER_CHAR("Date", &attrs[3], len);
break;
case NotificationAttributeIDPositiveActionLabel:
ESP_LOG_BUFFER_HEX("PActionLabel", &attrs[3], len);
break;
case NotificationAttributeIDNegativeActionLabel:
ESP_LOG_BUFFER_HEX("NActionLabel", &attrs[3], len);
break;
default:
ESP_LOG_BUFFER_HEX("unknownAttributeID", &attrs[3], len);
break;
}
attrs += (1 + 2 + len);
remian_attr_len -= (1 + 2 + len);
}
break;
}
case CommandIDGetAppAttributes:
ESP_LOGI(NimBLE_ANCS_TAG, "recevice APP Attributes response");
break;
case CommandIDPerformNotificationAction:
ESP_LOGI(NimBLE_ANCS_TAG, "recevice Perform Notification Action");
break;
default:
ESP_LOGI(NimBLE_ANCS_TAG, "unknown Command ID");
break;
}
}
char *Errcode_to_String(uint16_t status)
{
char *Errstr = NULL;
switch (status) {
case Unknown_command:
Errstr = "Unknown_command";
break;
case Invalid_command:
Errstr = "Invalid_command";
break;
case Invalid_parameter:
Errstr = "Invalid_parameter";
break;
case Action_failed:
Errstr = "Action_failed";
break;
default:
Errstr = "unknown_failed";
break;
}
return Errstr;
}

View File

@ -0,0 +1,118 @@
/*
* SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
//EventID values
typedef enum {
EventIDNotificationAdded = 0,
EventIDNotificationModified = 1,
EventIDNotificationRemoved = 2,
//Reserved EventID values = 3255
} ble_ancs_EventID;
//EventFlags
typedef enum {
EventFlagSilent = (1 << 0),
EventFlagImportant = (1 << 1),
EventFlagPreExisting = (1 << 2),
EventFlagPositiveAction = (1 << 3),
EventFlagNegativeAction = (1 << 4),
//Reserved EventFlags = (1 << 5)(1 << 7
}ble_ancs_EventFlags;
// CategoryID values
typedef enum {
CategoryIDOther = 0,
CategoryIDIncomingCall = 1,
CategoryIDMissedCall = 2,
CategoryIDVoicemail = 3,
CategoryIDSocial = 4,
CategoryIDSchedule = 5,
CategoryIDEmail = 6,
CategoryIDNews = 7,
CategoryIDHealthAndFitness = 8,
CategoryIDBusinessAndFinance = 9,
CategoryIDLocation = 10,
CategoryIDEntertainment = 11,
//Reserved CategoryID values = 12255
} ble_ancs_CategoryID;
//CommandID values
typedef enum {
CommandIDGetNotificationAttributes = 0,
CommandIDGetAppAttributes = 1,
CommandIDPerformNotificationAction = 2,
//Reserved CommandID values = 3255
} ble_ancs_CommandID;
//NotificationAttributeID
typedef enum {
NotificationAttributeIDAppIdentifier = 0,
NotificationAttributeIDTitle = 1, //(Needs to be followed by a 2-bytes max length parameter)
NotificationAttributeIDSubtitle = 2, //(Needs to be followed by a 2-bytes max length parameter)
NotificationAttributeIDMessage = 3, //(Needs to be followed by a 2-bytes max length parameter)
NotificationAttributeIDMessageSize = 4,
NotificationAttributeIDDate = 5,
NotificationAttributeIDPositiveActionLabel = 6,
NotificationAttributeIDNegativeActionLabel = 7,
//Reserved NotificationAttributeID values = 8255
} ble_ancs_NotificationAttributeID;
/*
Note: The format of the NotificationAttributeIDMessageSize constant is a string that represents the integral value
of the message size. The format of the NotificationAttributeIDDate constant is a string that uses the Unicode Technical
Standard (UTS) #35 date format pattern yyyyMMdd'T'HHmmSS. The format of all the other constants in Table 3-5 are UTF-8
strings.
*/
//ActionID values
typedef enum {
ActionIDPositive = 0,
ActionIDNegative = 1,
//Reserved ActionID values = 2255
} ble_ActionID;
//AppAttributeID Values
typedef enum {
AppAttributeIDDisplayName = 0,
//Reserved AppAttributeID values = 1255
} ble_AppAttributeID;
typedef struct {
uint8_t noti_attribute_id;
uint16_t attribute_len;
} ble_noti_attr_list_t;
typedef enum {
Unknown_command = (0xA0), //The commandID was not recognized by the NP.
Invalid_command = (0xA1), //The command was improperly formatted.
Invalid_parameter = (0xA2), // One of the parameters (for example, the NotificationUID) does not refer to an existing object on the NP.
Action_failed = (0xA3), //The action was not performed
} ble_ancs_error_code;
typedef enum {
attr_appidentifier_index = 0, //The commandID was not recognized by the NP.
attr_title_index,
attr_subtitle_index,
attr_message_index,
attr_messagesize_index,
attr_date_index,
attr_positiveactionlabel_index,
attr_negativeactionlabel_index,
} ble_ancs_attr_index;
#define ESP_NOTIFICATIONUID_LEN 4
char *EventID_to_String(uint8_t EventID);
char *CategoryID_to_String(uint8_t CategoryID);
void ble_receive_apple_notification_source(uint8_t *message, uint16_t message_len);
void ble_receive_apple_data_source(uint8_t *message, uint16_t message_len);
char *Errcode_to_String(uint16_t status);

View File

@ -0,0 +1,3 @@
dependencies:
nimble_peripheral_utils:
path: ${IDF_PATH}/examples/bluetooth/nimble/common/nimble_peripheral_utils

View File

@ -0,0 +1,771 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include "esp_log.h"
#include "nvs_flash.h"
/* BLE */
#include "nimble/nimble_port.h"
#include "nimble/nimble_port_freertos.h"
#include "host/ble_hs.h"
#include "host/util/util.h"
#include "console/console.h"
#include "services/gap/ble_svc_gap.h"
#include "nimble/ble.h"
#include "esp_peripheral.h"
#include "ble_ancs.h"
#include "esp_timer.h"
uint16_t notification_source_handle;
uint16_t data_source_handle;
uint16_t control_point_handle;
uint16_t svc_e_handle;
static void periodic_timer_callback(void* arg);
esp_timer_handle_t periodic_timer;
const esp_timer_create_args_t periodic_timer_args = {
.callback = &periodic_timer_callback,
/* name is optional, but may help identify the timer when debugging */
.name = "periodic"
};
struct data_source_buffer {
uint8_t buffer[1024];
uint16_t len;
};
static struct data_source_buffer data_buffer = {0};
static uint16_t MTU_size = 23;
//In its basic form, the ANCS exposes three characteristics:
// service UUID: 7905F431-B5CE-4E99-A40F-4B1E122D00D0
static ble_uuid128_t APPLE_NC_UUID = BLE_UUID128_INIT(
0xD0, 0x00, 0x2D, 0x12, 0x1E, 0x4B, 0x0F, 0xA4, 0x99, 0x4E, 0xCE, 0xB5, 0x31, 0xF4, 0x05, 0x79);
// Notification Source UUID: 9FBF120D-6301-42D9-8C58-25E699A21DBD(notifiable)
static ble_uuid128_t notification_source = BLE_UUID128_INIT(
0xbd, 0x1d, 0xa2, 0x99, 0xe6, 0x25, 0x58, 0x8c, 0xd9, 0x42, 0x01, 0x63, 0x0d, 0x12, 0xbf, 0x9f);
// Control Point UUID:69D1D8F3-45E1-49A8-9821-9BBDFDAAD9D9(writeable with response)
static ble_uuid128_t control_point = BLE_UUID128_INIT(
0xd9, 0xd9, 0xaa, 0xfd, 0xbd, 0x9b, 0x21, 0x98, 0xa8, 0x49, 0xe1, 0x45, 0xf3, 0xd8, 0xd1, 0x69);
// Data Source UUID:22EAC6E9-24D6-4BB5-BE44-B36ACE7C7BFB(notifiable)
static ble_uuid128_t data_source = BLE_UUID128_INIT(
0xfb, 0x7b, 0x7c, 0xce, 0x6a, 0xb3, 0x44, 0xbe, 0xb5, 0x4b, 0xd6, 0x24, 0xe9, 0xc6, 0xea, 0x22);
#if CONFIG_EXAMPLE_EXTENDED_ADV
static uint8_t ext_adv_pattern_1[] = {
0x02, 0x01, 0x06,
0x03, 0x03, 0xab, 0xcd,
0x03, 0x03, 0x18, 0x11,
0x0e, 0X09, 'n', 'i', 'm', 'b', 'l', 'e', '-', 'a', 'n', 'c', 's', '-', 'e',
};
#endif
static const char *tag = "NimBLE_ANCS";
static int ble_ancs_gap_event(struct ble_gap_event *event, void *arg);
static uint8_t own_addr_type;
void ble_store_config_init(void);
static ble_noti_attr_list_t p_attr[8] = {
[attr_appidentifier_index] = {
.noti_attribute_id = NotificationAttributeIDAppIdentifier,
.attribute_len = 0,
},
[attr_title_index] = {
.noti_attribute_id = NotificationAttributeIDTitle,
.attribute_len = 0xFFFF,
},
[attr_subtitle_index] = {
.noti_attribute_id = NotificationAttributeIDSubtitle,
.attribute_len = 0xFFFF,
},
[attr_message_index] = {
.noti_attribute_id = NotificationAttributeIDMessage,
.attribute_len = 0xFFFF,
},
[attr_messagesize_index] = {
.noti_attribute_id = NotificationAttributeIDMessageSize,
.attribute_len = 0,
},
[attr_date_index] = {
.noti_attribute_id = NotificationAttributeIDDate,
.attribute_len = 0,
},
[attr_positiveactionlabel_index] = {
.noti_attribute_id = NotificationAttributeIDPositiveActionLabel,
.attribute_len = 0,
},
[attr_negativeactionlabel_index] = {
.noti_attribute_id = NotificationAttributeIDNegativeActionLabel,
.attribute_len = 0,
},
};
void ble_get_notification_attributes(uint16_t conn_handle,
uint16_t control_point_handle,
uint8_t *notificationUID,
uint8_t num_attr,
ble_noti_attr_list_t *p_attr)
{
uint8_t cmd[600] = {0};
uint32_t index = 0;
cmd[index++] = CommandIDGetNotificationAttributes;
memcpy(&cmd[index], notificationUID, ESP_NOTIFICATIONUID_LEN);
index += ESP_NOTIFICATIONUID_LEN;
while (num_attr > 0) {
cmd[index++] = p_attr->noti_attribute_id;
if (p_attr->attribute_len > 0) {
cmd[index++] = (uint8_t)(p_attr->attribute_len & 0xFF);
cmd[index++] = (uint8_t)((p_attr->attribute_len >> 8) & 0xFF);
}
p_attr++;
num_attr--;
}
int rc = ble_gattc_write_flat(conn_handle,
control_point_handle,
cmd,
index,
NULL, // no callback
NULL); // no argument
if (rc != 0) {
MODLOG_DFLT(WARN, "Failed to write notification attribute request: rc=%d\n", rc);
}
}
void ble_perform_notification_action(uint8_t *notificationUID,
uint8_t ActionID,
uint16_t conn_handle,
uint16_t control_point_handle)
{
uint8_t buffer[600] = {0};
uint16_t index = 0;
buffer[index++] = CommandIDPerformNotificationAction; // define this as needed
memcpy(&buffer[index], notificationUID, ESP_NOTIFICATIONUID_LEN);
index += ESP_NOTIFICATIONUID_LEN;
buffer[index++] = ActionID;
int rc = ble_gattc_write_flat(conn_handle,
control_point_handle,
buffer,
index,
NULL, // no callback
NULL); // no callback argument
if (rc != 0) {
MODLOG_DFLT(WARN, "Failed to write perform action command: rc=%d\n", rc);
}
}
static void periodic_timer_callback(void* arg)
{
esp_timer_stop(periodic_timer);
if (data_buffer.len > 0) {
ble_receive_apple_data_source(data_buffer.buffer, data_buffer.len);
memset(data_buffer.buffer, 0, data_buffer.len);
data_buffer.len = 0;
}
}
static int
ancs_on_custom_subscribe(uint16_t conn_handle,
const struct ble_gatt_error *error,
struct ble_gatt_attr *attr,
void *arg)
{
MODLOG_DFLT(INFO,
"Subscribe to the custom subscribable characteristic complete; "
"status=%d conn_handle=%d",
error->status, conn_handle);
return 0;
}
int ancs_dsc_discovered_cb(uint16_t conn_handle, const struct ble_gatt_error *error,
uint16_t chr_val_handle, const struct ble_gatt_dsc *dsc,
void *arg)
{
int rc = 0;
uint8_t cccd_val[2] = {0x01, 0x00}; // Enable notification
if (error->status == 0) {
if (ble_uuid_cmp(&dsc->uuid.u, BLE_UUID16_DECLARE(BLE_GATT_DSC_CLT_CFG_UUID16)) == 0) {
rc = ble_gattc_write_flat(conn_handle,
dsc->handle,
cccd_val, sizeof(cccd_val),
ancs_on_custom_subscribe, NULL);
if (rc != 0) {
MODLOG_DFLT(WARN, "Failed to subscribe to (rc=%d)", rc);
return rc;
}
}
} else {
rc = error->status;
}
return rc;
}
static int
ancs_chr_discovered_cb(uint16_t conn_handle, const struct ble_gatt_error *error,
const struct ble_gatt_chr *chr, void *arg)
{
int rc = 0;
if (error->status == 0) {
/* Notification Source */
if ((chr->properties & BLE_GATT_CHR_PROP_NOTIFY) &&
ble_uuid_cmp(&chr->uuid.u, &notification_source.u) == 0) {
notification_source_handle = chr->val_handle;
rc = ble_gattc_disc_all_dscs(conn_handle, chr->val_handle, svc_e_handle, ancs_dsc_discovered_cb, NULL);
MODLOG_DFLT(INFO, "Found Notification Source characnteristic");
}
/* Data Source */
else if ((chr->properties & BLE_GATT_CHR_PROP_NOTIFY) &&
ble_uuid_cmp(&chr->uuid.u, &data_source.u) == 0) {
data_source_handle = chr->val_handle;
rc = ble_gattc_disc_all_dscs(conn_handle, chr->val_handle, svc_e_handle, ancs_dsc_discovered_cb, NULL);
MODLOG_DFLT(INFO, "Found Data Source characteristic");
}
/* Control Point */
else if ((chr->properties & BLE_GATT_CHR_PROP_WRITE) &&
ble_uuid_cmp(&chr->uuid.u, &control_point.u) == 0) {
control_point_handle = chr->val_handle;
MODLOG_DFLT(INFO, "Found Control Point characteristic");
}
} else{
rc = error->status;
}
return rc;
}
static int ancs_service_discovered_cb(uint16_t conn_handle, const struct ble_gatt_error *error,
const struct ble_gatt_svc *svc, void *arg)
{
int rc ;
if (error->status == 0) {
if (svc == NULL) {
MODLOG_DFLT(WARN, "ANCS service not found");
return -1;
}
svc_e_handle = svc->end_handle;
/* discover characteristics within this service range */
rc = ble_gattc_disc_all_chrs(conn_handle, svc->start_handle, svc->end_handle, ancs_chr_discovered_cb, NULL);
} else {
rc = error->status;
}
return rc;
}
#if NIMBLE_BLE_CONNECT
/**
* Logs information about a connection to the console.
*/
static void
ble_ancs_print_conn_desc(struct ble_gap_conn_desc *desc)
{
MODLOG_DFLT(INFO, "handle=%d our_ota_addr_type=%d our_ota_addr=",
desc->conn_handle, desc->our_ota_addr.type);
print_addr(desc->our_ota_addr.val);
MODLOG_DFLT(INFO, " our_id_addr_type=%d our_id_addr=",
desc->our_id_addr.type);
print_addr(desc->our_id_addr.val);
MODLOG_DFLT(INFO, " peer_ota_addr_type=%d peer_ota_addr=",
desc->peer_ota_addr.type);
print_addr(desc->peer_ota_addr.val);
MODLOG_DFLT(INFO, " peer_id_addr_type=%d peer_id_addr=",
desc->peer_id_addr.type);
print_addr(desc->peer_id_addr.val);
MODLOG_DFLT(INFO, " conn_itvl=%d conn_latency=%d supervision_timeout=%d "
"encrypted=%d authenticated=%d bonded=%d\n",
desc->conn_itvl, desc->conn_latency,
desc->supervision_timeout,
desc->sec_state.encrypted,
desc->sec_state.authenticated,
desc->sec_state.bonded);
}
#endif
#if CONFIG_EXAMPLE_EXTENDED_ADV
/**
* Enables advertising with the following parameters:
* o General discoverable mode.
* o Undirected connectable mode.
*/
static void
ext_ble_ancs_advertise(void)
{
struct ble_gap_ext_adv_params params;
struct os_mbuf *data;
uint8_t instance = 0;
int rc;
/* First check if any instance is already active */
if(ble_gap_ext_adv_active(instance)) {
return;
}
/* use defaults for non-set params */
memset (&params, 0, sizeof(params));
/* enable connectable advertising */
params.connectable = 1;
/* advertise using random addr */
params.own_addr_type = BLE_OWN_ADDR_PUBLIC;
params.primary_phy = BLE_HCI_LE_PHY_1M;
params.secondary_phy = BLE_HCI_LE_PHY_2M;
//params.tx_power = 127;
params.sid = 1;
params.itvl_min = BLE_GAP_ADV_FAST_INTERVAL1_MIN;
params.itvl_max = BLE_GAP_ADV_FAST_INTERVAL1_MIN;
/* configure instance 0 */
rc = ble_gap_ext_adv_configure(instance, &params, NULL,
ble_ancs_gap_event, NULL);
assert (rc == 0);
/* in this case only scan response is allowed */
/* get mbuf for scan rsp data */
data = os_msys_get_pkthdr(sizeof(ext_adv_pattern_1), 0);
assert(data);
/* fill mbuf with scan rsp data */
rc = os_mbuf_append(data, ext_adv_pattern_1, sizeof(ext_adv_pattern_1));
assert(rc == 0);
rc = ble_gap_ext_adv_set_data(instance, data);
assert (rc == 0);
/* start advertising */
rc = ble_gap_ext_adv_start(instance, 0, 0);
assert (rc == 0);
}
#else
/**
* Enables advertising with the following parameters:
* o General discoverable mode.
* o Undirected connectable mode.
*/
static void
ble_ancs_advertise(void)
{
struct ble_gap_adv_params adv_params;
struct ble_hs_adv_fields fields;
#if CONFIG_BT_NIMBLE_GAP_SERVICE
const char *name;
#endif
int rc;
/**
* Set the advertisement data included in our advertisements:
* o Flags (indicates advertisement type and other general info).
* o Advertising tx power.
* o Device name.
* o 16-bit service UUIDs (alert notifications).
*/
memset(&fields, 0, sizeof fields);
/* Advertise two flags:
* o Discoverability in forthcoming advertisement (general)
* o BLE-only (BR/EDR unsupported).
*/
fields.flags = BLE_HS_ADV_F_DISC_GEN |
BLE_HS_ADV_F_BREDR_UNSUP;
/* Indicate that the TX power level field should be included; have the
* stack fill this value automatically. This is done by assigning the
* special value BLE_HS_ADV_TX_PWR_LVL_AUTO.
*/
fields.tx_pwr_lvl_is_present = 1;
fields.tx_pwr_lvl = BLE_HS_ADV_TX_PWR_LVL_AUTO;
name = ble_svc_gap_device_name();
fields.name = (uint8_t *)name;
fields.name_len = strlen(name);
fields.name_is_complete = 1;
fields.num_uuids16 = 1;
fields.uuids16_is_complete = 1;
rc = ble_gap_adv_set_fields(&fields);
if (rc != 0) {
MODLOG_DFLT(ERROR, "error setting advertisement data; rc=%d\n", rc);
return;
}
/* Begin advertising. */
memset(&adv_params, 0, sizeof adv_params);
adv_params.conn_mode = BLE_GAP_CONN_MODE_UND;
adv_params.disc_mode = BLE_GAP_DISC_MODE_GEN;
rc = ble_gap_adv_start(own_addr_type, NULL, BLE_HS_FOREVER,
&adv_params, ble_ancs_gap_event, NULL);
if (rc != 0) {
MODLOG_DFLT(ERROR, "error enabling advertisement; rc=%d\n", rc);
return;
}
}
#endif
/**
* The nimble host executes this callback when a GAP event occurs. The
* application associates a GAP event callback with each connection that forms.
* ble_ancs uses the same callback for all connections.
*
* @param event The type of event being signalled.
* @param ctxt Various information pertaining to the event.
* @param arg Application-specified argument; unused by
* ble_ancs.
*
* @return 0 if the application successfully handled the
* event; nonzero on failure. The semantics
* of the return code is specific to the
* particular GAP event being signalled.
*/
static int
ble_ancs_gap_event(struct ble_gap_event *event, void *arg)
{
#if NIMBLE_BLE_CONNECT
struct ble_gap_conn_desc desc;
int rc;
#endif
switch (event->type) {
#if NIMBLE_BLE_CONNECT
case BLE_GAP_EVENT_CONNECT:
/* A new connection was established or a connection attempt failed. */
MODLOG_DFLT(INFO, "connection %s; status=%d ",
event->connect.status == 0 ? "established" : "failed",
event->connect.status);
if (event->connect.status == 0) {
rc = ble_gap_conn_find(event->connect.conn_handle, &desc);
assert(rc == 0);
ble_ancs_print_conn_desc(&desc);
}
MODLOG_DFLT(INFO, "\n");
if (event->connect.status != 0) {
/* Connection failed; resume advertising. */
#if CONFIG_EXAMPLE_EXTENDED_ADV
ext_ble_ancs_advertise();
#else
ble_ancs_advertise();
#endif
}
/** Initiate security - It will perform
* Pairing (Exchange keys)
* Bonding (Store keys)
* Encryption (Enable encryption)
* Will invoke event BLE_GAP_EVENT_ENC_CHANGE
**/
rc = ble_gap_security_initiate(event->connect.conn_handle);
if (rc != 0) {
MODLOG_DFLT(INFO, "Security could not be initiated, rc = %d\n", rc);
return ble_gap_terminate(event->connect.conn_handle,
BLE_ERR_REM_USER_CONN_TERM);
} else {
MODLOG_DFLT(INFO, "Connection secured\n");
}
return 0;
case BLE_GAP_EVENT_DISCONNECT:
MODLOG_DFLT(INFO, "disconnect; reason=%d ", event->disconnect.reason);
ble_ancs_print_conn_desc(&event->disconnect.conn);
MODLOG_DFLT(INFO, "\n");
/* Connection terminated; resume advertising. */
#if CONFIG_EXAMPLE_EXTENDED_ADV
ext_ble_ancs_advertise();
#else
ble_ancs_advertise();
#endif
return 0;
case BLE_GAP_EVENT_CONN_UPDATE:
/* The central has updated the connection parameters. */
MODLOG_DFLT(INFO, "connection updated; status=%d ",
event->conn_update.status);
rc = ble_gap_conn_find(event->conn_update.conn_handle, &desc);
assert(rc == 0);
ble_ancs_print_conn_desc(&desc);
MODLOG_DFLT(INFO, "\n");
return 0;
case BLE_GAP_EVENT_ADV_COMPLETE:
MODLOG_DFLT(INFO, "advertise complete; reason=%d",
event->adv_complete.reason);
#if CONFIG_EXAMPLE_EXTENDED_ADV
ext_ble_ancs_advertise();
#else
ble_ancs_advertise();
#endif
return 0;
case BLE_GAP_EVENT_ENC_CHANGE:
/* Encryption has been enabled or disabled for this connection. */
MODLOG_DFLT(INFO, "encryption change event; status=%d ",
event->enc_change.status);
rc = ble_gap_conn_find(event->enc_change.conn_handle, &desc);
assert(rc == 0);
ble_ancs_print_conn_desc(&desc);
MODLOG_DFLT(INFO, "\n");
rc = ble_gattc_disc_svc_by_uuid(event->connect.conn_handle, &APPLE_NC_UUID.u,
ancs_service_discovered_cb, NULL);
if (rc != 0) {
return rc;
}
return 0;
case BLE_GAP_EVENT_NOTIFY_TX:
MODLOG_DFLT(INFO, "notify_tx event; conn_handle=%d attr_handle=%d "
"status=%d is_indication=%d",
event->notify_tx.conn_handle,
event->notify_tx.attr_handle,
event->notify_tx.status,
event->notify_tx.indication);
return 0;
case BLE_GAP_EVENT_NOTIFY_RX:
/* Peer sent us a notification or indication. */
if (event->notify_rx.attr_handle == notification_source_handle) {
ble_receive_apple_notification_source(event->notify_rx.om->om_data, event->notify_rx.om->om_len);
uint8_t *notificationUID = &event->notify_rx.om->om_data[4];
if (event->notify_rx.om->om_data[0] == EventIDNotificationAdded &&
event->notify_rx.om->om_data[2] == CategoryIDIncomingCall) {
MODLOG_DFLT(INFO, "IncomingCall, reject");
ble_perform_notification_action(notificationUID, ActionIDNegative, event->notify_rx.conn_handle, control_point_handle);
} else if (event->notify_rx.om->om_data[0] == EventIDNotificationAdded) {
MODLOG_DFLT(INFO, "Get detailed information");
ble_get_notification_attributes(event->notify_rx.conn_handle, control_point_handle,
notificationUID, sizeof(p_attr)/sizeof(ble_noti_attr_list_t), p_attr);
}
} else if (event->notify_rx.attr_handle == data_source_handle) {
memcpy(&data_buffer.buffer[data_buffer.len],
event->notify_rx.om->om_data,
event->notify_rx.om->om_len);
data_buffer.len += event->notify_rx.om->om_len;
if (event->notify_rx.om->om_len == (MTU_size - 3)) {
esp_timer_start_periodic(periodic_timer, 500000);
} else {
esp_timer_stop(periodic_timer);
ble_receive_apple_data_source(data_buffer.buffer, data_buffer.len);
memset(data_buffer.buffer, 0, data_buffer.len);
data_buffer.len = 0;
}
} else {
MODLOG_DFLT(INFO, "unknown handle, receive notify value:" );
}
return 0;
case BLE_GAP_EVENT_SUBSCRIBE:
MODLOG_DFLT(INFO, "subscribe event; conn_handle=%d attr_handle=%d "
"reason=%d prevn=%d curn=%d previ=%d curi=%d\n",
event->subscribe.conn_handle,
event->subscribe.attr_handle,
event->subscribe.reason,
event->subscribe.prev_notify,
event->subscribe.cur_notify,
event->subscribe.prev_indicate,
event->subscribe.cur_indicate);
return 0;
case BLE_GAP_EVENT_MTU:
MTU_size = event->mtu.value;
MODLOG_DFLT(INFO, "mtu update event; conn_handle=%d cid=%d mtu=%d\n",
event->mtu.conn_handle,
event->mtu.channel_id,
event->mtu.value);
return 0;
case BLE_GAP_EVENT_REPEAT_PAIRING:
/* We already have a bond with the peer, but it is attempting to
* establish a new secure link. This app sacrifices security for
* convenience: just throw away the old bond and accept the new link.
*/
/* Delete the old bond. */
rc = ble_gap_conn_find(event->repeat_pairing.conn_handle, &desc);
assert(rc == 0);
ble_store_util_delete_peer(&desc.peer_id_addr);
/* Return BLE_GAP_REPEAT_PAIRING_RETRY to indicate that the host should
* continue with the pairing operation.
*/
return BLE_GAP_REPEAT_PAIRING_RETRY;
case BLE_GAP_EVENT_PASSKEY_ACTION:
ESP_LOGI(tag, "PASSKEY_ACTION_EVENT started");
struct ble_sm_io pkey = {0};
int key = 0;
if (event->passkey.params.action == BLE_SM_IOACT_DISP) {
pkey.action = event->passkey.params.action;
pkey.passkey = 123456; // This is the passkey to be entered on peer
ESP_LOGI(tag, "Enter passkey %" PRIu32 "on the peer side", pkey.passkey);
rc = ble_sm_inject_io(event->passkey.conn_handle, &pkey);
ESP_LOGI(tag, "ble_sm_inject_io result: %d", rc);
} else if (event->passkey.params.action == BLE_SM_IOACT_NUMCMP) {
ESP_LOGI(tag, "Passkey on device's display: %" PRIu32 , event->passkey.params.numcmp);
ESP_LOGI(tag, "Accept or reject the passkey through console in this format -> key Y or key N");
pkey.action = event->passkey.params.action;
if (scli_receive_key(&key)) {
pkey.numcmp_accept = key;
} else {
pkey.numcmp_accept = 0;
ESP_LOGE(tag, "Timeout! Rejecting the key");
}
rc = ble_sm_inject_io(event->passkey.conn_handle, &pkey);
ESP_LOGI(tag, "ble_sm_inject_io result: %d", rc);
} else if (event->passkey.params.action == BLE_SM_IOACT_OOB) {
static uint8_t tem_oob[16] = {0};
pkey.action = event->passkey.params.action;
for (int i = 0; i < 16; i++) {
pkey.oob[i] = tem_oob[i];
}
rc = ble_sm_inject_io(event->passkey.conn_handle, &pkey);
ESP_LOGI(tag, "ble_sm_inject_io result: %d", rc);
} else if (event->passkey.params.action == BLE_SM_IOACT_INPUT) {
ESP_LOGI(tag, "Enter the passkey through console in this format-> key 123456");
pkey.action = event->passkey.params.action;
if (scli_receive_key(&key)) {
pkey.passkey = key;
} else {
pkey.passkey = 0;
ESP_LOGE(tag, "Timeout! Passing 0 as the key");
}
rc = ble_sm_inject_io(event->passkey.conn_handle, &pkey);
ESP_LOGI(tag, "ble_sm_inject_io result: %d", rc);
}
return 0;
#endif
}
return 0;
}
static void
ble_ancs_on_reset(int reason)
{
MODLOG_DFLT(ERROR, "Resetting state; reason=%d\n", reason);
}
static void
ble_ancs_on_sync(void)
{
int rc;
rc = ble_hs_util_ensure_addr(0);
assert(rc == 0);
/* Figure out address to use while advertising (no privacy for now) */
rc = ble_hs_id_infer_auto(0, &own_addr_type);
if (rc != 0) {
MODLOG_DFLT(ERROR, "error determining address type; rc=%d\n", rc);
return;
}
/* Printing ADDR */
uint8_t addr_val[6] = {0};
rc = ble_hs_id_copy_addr(own_addr_type, addr_val, NULL);
MODLOG_DFLT(INFO, "Device Address: ");
print_addr(addr_val);
MODLOG_DFLT(INFO, "\n");
/* Begin advertising. */
#if CONFIG_EXAMPLE_EXTENDED_ADV
ext_ble_ancs_advertise();
#else
ble_ancs_advertise();
#endif
}
void ble_ancs_host_task(void *param)
{
ESP_LOGI(tag, "BLE Host Task Started");
/* This function will return only when nimble_port_stop() is executed */
nimble_port_run();
nimble_port_freertos_deinit();
}
void
app_main(void)
{
int rc;
/* Initialize NVS — it is used to store PHY calibration data */
esp_err_t 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);
ret = nimble_port_init();
if (ret != ESP_OK) {
ESP_LOGE(tag, "Failed to init nimble %d ", ret);
return;
}
/* Initialize the NimBLE host configuration. */
ble_hs_cfg.reset_cb = ble_ancs_on_reset;
ble_hs_cfg.sync_cb = ble_ancs_on_sync;
ble_hs_cfg.store_status_cb = ble_store_util_status_rr;
ble_hs_cfg.sm_bonding = 1;
/* Enable the appropriate bit masks to make sure the keys
* that are needed are exchanged
*/
ble_hs_cfg.sm_our_key_dist |= BLE_SM_PAIR_KEY_DIST_ENC;
ble_hs_cfg.sm_their_key_dist |= BLE_SM_PAIR_KEY_DIST_ENC;
ble_hs_cfg.sm_sc = 0;
/* Set the default device name. */
rc = ble_svc_gap_device_name_set("nimble-ancs");
assert(rc == 0);
/* XXX Need to have template for store */
ble_store_config_init();
nimble_port_freertos_init(ble_ancs_host_task);
}

View File

@ -0,0 +1,13 @@
# Override some defaults so BT stack is enabled
# in this example
#
# BT config
#
CONFIG_BT_ENABLED=y
CONFIG_BTDM_CTRL_MODE_BLE_ONLY=y
CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY=n
CONFIG_BTDM_CTRL_MODE_BTDM=n
CONFIG_BT_BLUEDROID_ENABLED=n
CONFIG_BT_NIMBLE_ENABLED=y
CONFIG_BT_NIMBLE_NVS_PERSIST=y

View File

@ -792,6 +792,7 @@ examples/bluetooth/esp_ble_mesh/aligenie_demo/components/vendor_model/include/ge
examples/bluetooth/esp_ble_mesh/aligenie_demo/components/vendor_model/include/genie_slist.h
examples/bluetooth/esp_ble_mesh/aligenie_demo/components/vendor_model/include/genie_timer.h
examples/bluetooth/esp_ble_mesh/aligenie_demo/components/vendor_model/include/genie_util.h
examples/bluetooth/nimble/ble_ancs/main/main.c
examples/bluetooth/nimble/ble_gattc_gatts_coex/main/blecoex.h
examples/bluetooth/nimble/blecent/main/blecent.h
examples/bluetooth/nimble/blecent/main/main.c