mirror of
https://github.com/espressif/esp-idf.git
synced 2026-10-02 11:10:54 +03:00
feat(examples): add Bluedroid and NimBLE BLE AMS client examples
Add Apple Media Service client demos that discover AMS on iOS, subscribe to media entity updates, and expose remote playback control helpers.
This commit is contained in:
@@ -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.22)
|
||||
|
||||
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_ams)
|
||||
@@ -0,0 +1,122 @@
|
||||
| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C5 | ESP32-C6 | ESP32-C61 | ESP32-H2 | ESP32-H21 | ESP32-H4 | ESP32-S3 | ESP32-S31 |
|
||||
| ----------------- | ----- | -------- | -------- | -------- | -------- | --------- | -------- | --------- | -------- | -------- | --------- |
|
||||
|
||||
# ESP-IDF BLE AMS Example
|
||||
|
||||
The purpose of the Apple Media Service (AMS) is to give Bluetooth accessories (that connect to iOS devices through a Bluetooth low-energy link) a simple and convenient way to control media apps and access information about the media state of the connected iOS device.
|
||||
|
||||
## Flow Diagram
|
||||
|
||||
```
|
||||
┌──────────────┐ ┌──────────────┐
|
||||
│ ESP32 │ │ iOS Device │
|
||||
│ (AMS Client) │ │ (AMS Server) │
|
||||
└──────┬───────┘ └──────┬───────┘
|
||||
│ │
|
||||
│ ─────────── Connection ─────────── │
|
||||
│ │
|
||||
│ 1. Start Advertising │
|
||||
│ ───────────────────────────────────────────────> │
|
||||
│ │
|
||||
│ 2. iOS connects & pairs │
|
||||
│ <─────────────────────────────────────────────── │
|
||||
│ │
|
||||
│ ─────────── Service Discovery ─────────── │
|
||||
│ │
|
||||
│ 3. Discover AMS Service │
|
||||
│ (UUID: 89D3502B-0F36-433A-8EF4-C502AD55F8DC) │
|
||||
│ ───────────────────────────────────────────────> │
|
||||
│ │
|
||||
│ 4. Subscribe to Remote Command │
|
||||
│ (UUID: 9B3C81D8-57B1-4A8A-B8DF-0E56F7CA51C2) │
|
||||
│ ───────────────────────────────────────────────> │
|
||||
│ │
|
||||
│ 5. Subscribe to Entity Update │
|
||||
│ (UUID: 2F7CABCE-808D-411F-9A0C-BB92BA96C102) │
|
||||
│ ───────────────────────────────────────────────> │
|
||||
│ │
|
||||
│ 6. Register entity/attribute pairs │
|
||||
│ (Write to Entity Update) │
|
||||
│ ───────────────────────────────────────────────> │
|
||||
│ │
|
||||
│ ─────────── Media Update Flow ─────────── │
|
||||
│ │
|
||||
│ 7. Media state changes on iOS │
|
||||
│ (Track, Player, Queue updates) │
|
||||
│ <─────────────────────────────────────────────── │
|
||||
│ │
|
||||
│ 8. Optional: read full truncated value │
|
||||
│ (Write + Read Entity Attribute) │
|
||||
│ ───────────────────────────────────────────────> │
|
||||
│ │
|
||||
┌──────┴───────┐ ┌──────┴───────┐
|
||||
│ ESP32 │ │ iOS Device │
|
||||
└──────────────┘ └──────────────┘
|
||||
```
|
||||
|
||||
## 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 Media Service is a primary service whose service UUID is:
|
||||
|
||||
`89D3502B-0F36-433A-8EF4-C502AD55F8DC`
|
||||
|
||||
Only one instance of the AMS may be present on an MS. Due to the nature of iOS, the AMS is not guaranteed to always be present. As a result, the MR 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 AMS at any time.
|
||||
|
||||
In its basic form, the AMS exposes three characteristics:
|
||||
|
||||
* Remote Command: UUID `9B3C81D8-57B1-4A8A-B8DF-0E56F7CA51C2` (writeable, notifiable)
|
||||
* Entity Update: UUID `2F7CABCE-808D-411F-9A0C-BB92BA96C102` (writeable with response, notifiable)
|
||||
* Entity Attribute: UUID `C6B2F38C-23AB-46D8-A6AB-A3A870BBD5D7` (readable, writeable)
|
||||
|
||||
After pairing, start playing music on the iOS device. The example subscribes to track, player, and queue entity updates and prints media metadata to the console.
|
||||
|
||||
The example uses legacy connectable advertising (`ESP_BLE_AMS`).
|
||||
|
||||
Advertising data matches `ble_ancs`: HID appearance, 16-bit service UUID `0x1812`, and device name in the scan response.
|
||||
|
||||
To send a remote command from application code, call `esp_send_remote_command()` with a value from `esp_remote_command_id_t`, for example `RemoteCommandIDTogglePlayPause`.
|
||||
|
||||
### 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 (918) BLE_AMS: app_main init bluetooth
|
||||
I (1018) BLE_AMS: REG_EVT
|
||||
I (1058) BLE_AMS: advertising start success
|
||||
I (5234) BLE_AMS: ESP_GATTC_OPEN_EVT
|
||||
I (5456) BLE_AMS: ESP_GATTC_CFG_MTU_EVT, Status 0, MTU 517, conn_id 0
|
||||
I (5678) BLE_AMS: Found Remote Command characteristic
|
||||
I (5678) BLE_AMS: Found Entity Update characteristic
|
||||
I (5680) BLE_AMS: Found Entity Attribute characteristic
|
||||
I (5720) BLE_AMS: Entity Update notifications enabled
|
||||
I (5721) BLE_AMS: Subscribe track entity updates
|
||||
I (5800) BLE_AMS: Supported remote commands (6):
|
||||
I (5801) BLE_AMS: [0] Play
|
||||
I (5802) BLE_AMS: [1] Pause
|
||||
I (5900) BLE_AMS: Entity update: Track / Title, flags=0x00, len=12
|
||||
I (5901) BLE_AMS: Value: Example Song
|
||||
```
|
||||
|
||||
## 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.
|
||||
@@ -0,0 +1,3 @@
|
||||
idf_component_register(SRCS "ble_ams_demo.c" "ble_ams.c"
|
||||
PRIV_REQUIRES bt nvs_flash
|
||||
INCLUDE_DIRS ".")
|
||||
@@ -0,0 +1,236 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "esp_log.h"
|
||||
#include "ble_ams.h"
|
||||
|
||||
#define BLE_AMS_TAG "BLE_AMS"
|
||||
|
||||
const char *RemoteCommandID_to_String(uint8_t command_id)
|
||||
{
|
||||
switch (command_id) {
|
||||
case RemoteCommandIDPlay:
|
||||
return "Play";
|
||||
case RemoteCommandIDPause:
|
||||
return "Pause";
|
||||
case RemoteCommandIDTogglePlayPause:
|
||||
return "TogglePlayPause";
|
||||
case RemoteCommandIDNextTrack:
|
||||
return "NextTrack";
|
||||
case RemoteCommandIDPreviousTrack:
|
||||
return "PreviousTrack";
|
||||
case RemoteCommandIDVolumeUp:
|
||||
return "VolumeUp";
|
||||
case RemoteCommandIDVolumeDown:
|
||||
return "VolumeDown";
|
||||
case RemoteCommandIDAdvanceRepeatMode:
|
||||
return "AdvanceRepeatMode";
|
||||
case RemoteCommandIDAdvanceShuffleMode:
|
||||
return "AdvanceShuffleMode";
|
||||
case RemoteCommandIDSkipForward:
|
||||
return "SkipForward";
|
||||
case RemoteCommandIDSkipBackward:
|
||||
return "SkipBackward";
|
||||
case RemoteCommandIDLikeTrack:
|
||||
return "LikeTrack";
|
||||
case RemoteCommandIDDislikeTrack:
|
||||
return "DislikeTrack";
|
||||
case RemoteCommandIDBookmarkTrack:
|
||||
return "BookmarkTrack";
|
||||
default:
|
||||
return "Unknown RemoteCommandID";
|
||||
}
|
||||
}
|
||||
|
||||
const char *EntityID_to_String(uint8_t entity_id)
|
||||
{
|
||||
switch (entity_id) {
|
||||
case EntityIDPlayer:
|
||||
return "Player";
|
||||
case EntityIDQueue:
|
||||
return "Queue";
|
||||
case EntityIDTrack:
|
||||
return "Track";
|
||||
default:
|
||||
return "Unknown EntityID";
|
||||
}
|
||||
}
|
||||
|
||||
const char *AttributeID_to_String(uint8_t entity_id, uint8_t attribute_id)
|
||||
{
|
||||
switch (entity_id) {
|
||||
case EntityIDPlayer:
|
||||
switch (attribute_id) {
|
||||
case PlayerAttributeIDName:
|
||||
return "PlayerName";
|
||||
case PlayerAttributeIDPlaybackInfo:
|
||||
return "PlaybackInfo";
|
||||
case PlayerAttributeIDVolume:
|
||||
return "Volume";
|
||||
default:
|
||||
return "Unknown PlayerAttributeID";
|
||||
}
|
||||
case EntityIDQueue:
|
||||
switch (attribute_id) {
|
||||
case QueueAttributeIDIndex:
|
||||
return "QueueIndex";
|
||||
case QueueAttributeIDCount:
|
||||
return "QueueCount";
|
||||
case QueueAttributeIDShuffleMode:
|
||||
return "ShuffleMode";
|
||||
case QueueAttributeIDRepeatMode:
|
||||
return "RepeatMode";
|
||||
default:
|
||||
return "Unknown QueueAttributeID";
|
||||
}
|
||||
case EntityIDTrack:
|
||||
switch (attribute_id) {
|
||||
case TrackAttributeIDArtist:
|
||||
return "Artist";
|
||||
case TrackAttributeIDAlbum:
|
||||
return "Album";
|
||||
case TrackAttributeIDTitle:
|
||||
return "Title";
|
||||
case TrackAttributeIDDuration:
|
||||
return "Duration";
|
||||
default:
|
||||
return "Unknown TrackAttributeID";
|
||||
}
|
||||
default:
|
||||
return "Unknown AttributeID";
|
||||
}
|
||||
}
|
||||
|
||||
const char *PlaybackState_to_String(uint8_t playback_state)
|
||||
{
|
||||
switch (playback_state) {
|
||||
case PlaybackStatePaused:
|
||||
return "Paused";
|
||||
case PlaybackStatePlaying:
|
||||
return "Playing";
|
||||
case PlaybackStateRewinding:
|
||||
return "Rewinding";
|
||||
case PlaybackStateFastForwarding:
|
||||
return "FastForwarding";
|
||||
default:
|
||||
return "Unknown PlaybackState";
|
||||
}
|
||||
}
|
||||
|
||||
static void log_playback_info(const uint8_t *value, uint16_t value_len)
|
||||
{
|
||||
if (!value || value_len == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
char buffer[256];
|
||||
uint16_t copy_len = value_len < (sizeof(buffer) - 1) ? value_len : (sizeof(buffer) - 1);
|
||||
memcpy(buffer, value, copy_len);
|
||||
buffer[copy_len] = '\0';
|
||||
|
||||
char *state = buffer;
|
||||
char *rate = NULL;
|
||||
char *elapsed = NULL;
|
||||
char *separator = strchr(state, ',');
|
||||
if (separator) {
|
||||
*separator = '\0';
|
||||
rate = separator + 1;
|
||||
separator = strchr(rate, ',');
|
||||
if (separator) {
|
||||
*separator = '\0';
|
||||
elapsed = separator + 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (state[0] != '\0') {
|
||||
int playback_state = atoi(state);
|
||||
ESP_LOGI(BLE_AMS_TAG, " PlaybackState: %s", PlaybackState_to_String((uint8_t)playback_state));
|
||||
}
|
||||
if (rate && rate[0] != '\0') {
|
||||
ESP_LOGI(BLE_AMS_TAG, " PlaybackRate: %s", rate);
|
||||
}
|
||||
if (elapsed && elapsed[0] != '\0') {
|
||||
ESP_LOGI(BLE_AMS_TAG, " ElapsedTime: %s s", elapsed);
|
||||
}
|
||||
}
|
||||
|
||||
void esp_receive_remote_command_notification(uint8_t *message, uint16_t message_len)
|
||||
{
|
||||
if (!message || message_len == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGI(BLE_AMS_TAG, "Supported remote commands (%u):", message_len);
|
||||
for (uint16_t i = 0; i < message_len; i++) {
|
||||
ESP_LOGI(BLE_AMS_TAG, " [%u] %s", i, RemoteCommandID_to_String(message[i]));
|
||||
}
|
||||
}
|
||||
|
||||
void esp_receive_entity_update(uint8_t *message, uint16_t message_len)
|
||||
{
|
||||
if (!message || message_len < 3) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t entity_id = message[0];
|
||||
uint8_t attribute_id = message[1];
|
||||
uint8_t flags = message[2];
|
||||
uint8_t *value = &message[3];
|
||||
uint16_t value_len = message_len - 3;
|
||||
|
||||
ESP_LOGI(BLE_AMS_TAG, "Entity update: %s / %s, flags=0x%02x, len=%u",
|
||||
EntityID_to_String(entity_id),
|
||||
AttributeID_to_String(entity_id, attribute_id),
|
||||
flags,
|
||||
value_len);
|
||||
|
||||
if (entity_id == EntityIDPlayer && attribute_id == PlayerAttributeIDPlaybackInfo) {
|
||||
log_playback_info(value, value_len);
|
||||
return;
|
||||
}
|
||||
|
||||
if (value_len > 0) {
|
||||
ESP_LOG_BUFFER_CHAR("Value", value, value_len);
|
||||
}
|
||||
|
||||
if (flags & EntityUpdateFlagTruncated) {
|
||||
ESP_LOGI(BLE_AMS_TAG, "Value truncated; read full value from Entity Attribute characteristic");
|
||||
}
|
||||
}
|
||||
|
||||
void esp_receive_entity_attribute_value(uint8_t entity_id, uint8_t attribute_id,
|
||||
uint8_t *message, uint16_t message_len)
|
||||
{
|
||||
ESP_LOGI(BLE_AMS_TAG, "Full entity attribute: %s / %s, len=%u",
|
||||
EntityID_to_String(entity_id),
|
||||
AttributeID_to_String(entity_id, attribute_id),
|
||||
message_len);
|
||||
|
||||
if (entity_id == EntityIDPlayer && attribute_id == PlayerAttributeIDPlaybackInfo) {
|
||||
log_playback_info(message, message_len);
|
||||
return;
|
||||
}
|
||||
|
||||
if (message && message_len > 0) {
|
||||
ESP_LOG_BUFFER_CHAR("Value", message, message_len);
|
||||
}
|
||||
}
|
||||
|
||||
const char *Errcode_to_String(uint16_t status)
|
||||
{
|
||||
switch (status) {
|
||||
case Invalid_state:
|
||||
return "Invalid_state";
|
||||
case Invalid_command:
|
||||
return "Invalid_command";
|
||||
case Absent_attribute:
|
||||
return "Absent_attribute";
|
||||
default:
|
||||
return "unknown_failed";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include "esp_err.h"
|
||||
|
||||
// RemoteCommandID values (Table A-1)
|
||||
typedef enum {
|
||||
RemoteCommandIDPlay = 0,
|
||||
RemoteCommandIDPause = 1,
|
||||
RemoteCommandIDTogglePlayPause = 2,
|
||||
RemoteCommandIDNextTrack = 3,
|
||||
RemoteCommandIDPreviousTrack = 4,
|
||||
RemoteCommandIDVolumeUp = 5,
|
||||
RemoteCommandIDVolumeDown = 6,
|
||||
RemoteCommandIDAdvanceRepeatMode = 7,
|
||||
RemoteCommandIDAdvanceShuffleMode = 8,
|
||||
RemoteCommandIDSkipForward = 9,
|
||||
RemoteCommandIDSkipBackward = 10,
|
||||
RemoteCommandIDLikeTrack = 11,
|
||||
RemoteCommandIDDislikeTrack = 12,
|
||||
RemoteCommandIDBookmarkTrack = 13,
|
||||
} esp_remote_command_id_t;
|
||||
|
||||
// EntityID values (Table A-2)
|
||||
typedef enum {
|
||||
EntityIDPlayer = 0,
|
||||
EntityIDQueue = 1,
|
||||
EntityIDTrack = 2,
|
||||
} esp_entity_id_t;
|
||||
|
||||
// EntityUpdateFlags (Table A-3)
|
||||
typedef enum {
|
||||
EntityUpdateFlagTruncated = (1 << 0),
|
||||
} esp_entity_update_flag_t;
|
||||
|
||||
// PlayerAttributeID values (Table A-4)
|
||||
typedef enum {
|
||||
PlayerAttributeIDName = 0,
|
||||
PlayerAttributeIDPlaybackInfo = 1,
|
||||
PlayerAttributeIDVolume = 2,
|
||||
} esp_player_attribute_id_t;
|
||||
|
||||
// QueueAttributeID values (Table A-5)
|
||||
typedef enum {
|
||||
QueueAttributeIDIndex = 0,
|
||||
QueueAttributeIDCount = 1,
|
||||
QueueAttributeIDShuffleMode = 2,
|
||||
QueueAttributeIDRepeatMode = 3,
|
||||
} esp_queue_attribute_id_t;
|
||||
|
||||
// TrackAttributeID values (Table A-8)
|
||||
typedef enum {
|
||||
TrackAttributeIDArtist = 0,
|
||||
TrackAttributeIDAlbum = 1,
|
||||
TrackAttributeIDTitle = 2,
|
||||
TrackAttributeIDDuration = 3,
|
||||
} esp_track_attribute_id_t;
|
||||
|
||||
typedef enum {
|
||||
PlaybackStatePaused = 0,
|
||||
PlaybackStatePlaying = 1,
|
||||
PlaybackStateRewinding = 2,
|
||||
PlaybackStateFastForwarding = 3,
|
||||
} esp_playback_state_t;
|
||||
|
||||
typedef enum {
|
||||
Invalid_state = 0xA0,
|
||||
Invalid_command = 0xA1,
|
||||
Absent_attribute = 0xA2,
|
||||
} esp_ams_error_code_t;
|
||||
|
||||
const char *RemoteCommandID_to_String(uint8_t command_id);
|
||||
const char *EntityID_to_String(uint8_t entity_id);
|
||||
const char *AttributeID_to_String(uint8_t entity_id, uint8_t attribute_id);
|
||||
const char *PlaybackState_to_String(uint8_t playback_state);
|
||||
const char *Errcode_to_String(uint16_t status);
|
||||
|
||||
void esp_receive_remote_command_notification(uint8_t *message, uint16_t message_len);
|
||||
void esp_receive_entity_update(uint8_t *message, uint16_t message_len);
|
||||
void esp_receive_entity_attribute_value(uint8_t entity_id, uint8_t attribute_id,
|
||||
uint8_t *message, uint16_t message_len);
|
||||
|
||||
esp_err_t esp_send_remote_command(uint8_t command_id);
|
||||
@@ -0,0 +1,855 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*/
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <string.h>
|
||||
#include "esp_log.h"
|
||||
#include "esp_timer.h"
|
||||
#include "nvs_flash.h"
|
||||
#include "esp_bt.h"
|
||||
|
||||
#include "esp_gap_ble_api.h"
|
||||
#include "esp_bt_defs.h"
|
||||
#include "esp_bt_main.h"
|
||||
#include "esp_bt_device.h"
|
||||
#include "esp_gattc_api.h"
|
||||
#include "esp_gatt_defs.h"
|
||||
#include "esp_gatt_common_api.h"
|
||||
#include "ble_ams.h"
|
||||
|
||||
#define BLE_AMS_DEMO_TAG "BLE_AMS"
|
||||
#define EXAMPLE_DEVICE_NAME "ESP_BLE_AMS"
|
||||
#define PROFILE_A_APP_ID 0
|
||||
#define PROFILE_NUM 1
|
||||
#define ADV_CONFIG_FLAG (1 << 0)
|
||||
#define SCAN_RSP_CONFIG_FLAG (1 << 1)
|
||||
#define INVALID_HANDLE 0
|
||||
#define AMS_ENTITY_UPDATE_CCCD_DELAY_US (500 * 1000)
|
||||
#define ENTITY_ATTR_REQ_QUEUE_LEN 4
|
||||
#define ENTITY_UPDATE_SUB_CMD_MAX_LEN 8
|
||||
#define ENTITY_UPDATE_SUB_QUEUE_LEN 3
|
||||
|
||||
static uint8_t adv_config_done = 0;
|
||||
static bool get_service = false;
|
||||
static bool entity_update_notify_enabled = false;
|
||||
static esp_gattc_char_elem_t *char_elem_result = NULL;
|
||||
static uint16_t remote_command_cccd_handle = 0;
|
||||
static uint16_t entity_update_cccd_handle = 0;
|
||||
static uint16_t pending_cccd_char_handle = 0;
|
||||
static esp_gatt_if_t pending_entity_update_gattc_if = ESP_GATT_IF_NONE;
|
||||
static esp_timer_handle_t entity_update_cccd_delay_timer;
|
||||
|
||||
static esp_bt_uuid_t cccd_uuid = {
|
||||
.len = ESP_UUID_LEN_16,
|
||||
.uuid = { .uuid16 = ESP_GATT_UUID_CHAR_CLIENT_CONFIG },
|
||||
};
|
||||
|
||||
static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param);
|
||||
static void ams_schedule_entity_update_notify(esp_gatt_if_t gattc_if);
|
||||
|
||||
// Apple Media Service UUID: 89D3502B-0F36-433A-8EF4-C502AD55F8DC
|
||||
static uint8_t Apple_MS_UUID[16] = {0xDC, 0xF8, 0x55, 0xAD, 0x02, 0xC5, 0xF4, 0x8E, 0x3A, 0x43, 0x36, 0x0F, 0x2B, 0x50, 0xD3, 0x89};
|
||||
// Remote Command UUID: 9B3C81D8-57B1-4A8A-B8DF-0E56F7CA51C2 (writeable, notifiable)
|
||||
static uint8_t remote_command_uuid[16] = {0xC2, 0x51, 0xCA, 0xF7, 0x56, 0x0E, 0xDF, 0xB8, 0x8A, 0x4A, 0xB1, 0x57, 0xD8, 0x81, 0x3C, 0x9B};
|
||||
// Entity Update UUID: 2F7CABCE-808D-411F-9A0C-BB92BA96C102 (writeable with response, notifiable)
|
||||
static uint8_t entity_update_uuid[16] = {0x02, 0xC1, 0x96, 0xBA, 0x92, 0xBB, 0x0C, 0x9A, 0x1F, 0x41, 0x8D, 0x80, 0xCE, 0xAB, 0x7C, 0x2F};
|
||||
// Entity Attribute UUID: C6B2F38C-23AB-46D8-A6AB-A3A870BBD5D7 (readable, writeable)
|
||||
static uint8_t entity_attribute_uuid[16] = {0xD7, 0xD5, 0xBB, 0x70, 0xA8, 0xA3, 0xAB, 0xA6, 0xD8, 0x46, 0xAB, 0x23, 0x8C, 0xF3, 0xB2, 0xC6};
|
||||
|
||||
static esp_bt_uuid_t apple_ms_uuid = {
|
||||
.len = ESP_UUID_LEN_128,
|
||||
};
|
||||
|
||||
static uint8_t hidd_service_uuid128[] = {
|
||||
0xfb, 0x34, 0x9b, 0x5f, 0x80, 0x00, 0x00, 0x80, 0x00, 0x10, 0x00, 0x00, 0x12, 0x18, 0x00, 0x00,
|
||||
};
|
||||
|
||||
static esp_ble_adv_data_t adv_config = {
|
||||
.set_scan_rsp = false,
|
||||
.include_txpower = false,
|
||||
.min_interval = ESP_BLE_GAP_CONN_ITVL_MS(7.5),
|
||||
.max_interval = ESP_BLE_GAP_CONN_ITVL_MS(20),
|
||||
.appearance = ESP_BLE_APPEARANCE_GENERIC_HID,
|
||||
.service_uuid_len = sizeof(hidd_service_uuid128),
|
||||
.p_service_uuid = hidd_service_uuid128,
|
||||
.flag = (ESP_BLE_ADV_FLAG_GEN_DISC | ESP_BLE_ADV_FLAG_BREDR_NOT_SPT),
|
||||
};
|
||||
|
||||
static esp_ble_adv_data_t scan_rsp_config = {
|
||||
.set_scan_rsp = true,
|
||||
.include_name = true,
|
||||
};
|
||||
|
||||
static esp_ble_adv_params_t adv_params = {
|
||||
.adv_int_min = ESP_BLE_GAP_ADV_ITVL_MS(160),
|
||||
.adv_int_max = ESP_BLE_GAP_ADV_ITVL_MS(160),
|
||||
.adv_type = ADV_TYPE_IND,
|
||||
.own_addr_type = BLE_ADDR_TYPE_PUBLIC,
|
||||
.channel_map = ADV_CHNL_ALL,
|
||||
.adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY,
|
||||
};
|
||||
|
||||
struct gattc_profile_inst {
|
||||
esp_gattc_cb_t gattc_cb;
|
||||
uint16_t gattc_if;
|
||||
uint16_t app_id;
|
||||
uint16_t conn_id;
|
||||
uint16_t service_start_handle;
|
||||
uint16_t service_end_handle;
|
||||
uint16_t remote_command_handle;
|
||||
uint16_t entity_update_handle;
|
||||
uint16_t entity_attribute_handle;
|
||||
esp_bd_addr_t remote_bda;
|
||||
uint16_t MTU_size;
|
||||
};
|
||||
|
||||
static struct gattc_profile_inst gl_profile_tab[PROFILE_NUM] = {
|
||||
[PROFILE_A_APP_ID] = {
|
||||
.gattc_cb = gattc_profile_event_handler,
|
||||
.gattc_if = ESP_GATT_IF_NONE,
|
||||
},
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
uint8_t entity_id;
|
||||
uint8_t attribute_id;
|
||||
} entity_attr_req_t;
|
||||
|
||||
typedef struct {
|
||||
uint8_t cmd_len;
|
||||
uint8_t cmd[ENTITY_UPDATE_SUB_CMD_MAX_LEN];
|
||||
} entity_update_sub_req_t;
|
||||
|
||||
static uint8_t pending_entity_id = 0;
|
||||
static uint8_t pending_attribute_id = 0;
|
||||
static bool entity_attr_req_in_flight = false;
|
||||
static entity_attr_req_t entity_attr_req_queue[ENTITY_ATTR_REQ_QUEUE_LEN];
|
||||
static uint8_t entity_attr_req_head = 0;
|
||||
static uint8_t entity_attr_req_tail = 0;
|
||||
static uint8_t entity_attr_req_count = 0;
|
||||
|
||||
static bool entity_update_sub_in_flight = false;
|
||||
static entity_update_sub_req_t entity_update_sub_queue[ENTITY_UPDATE_SUB_QUEUE_LEN];
|
||||
static uint8_t entity_update_sub_head = 0;
|
||||
static uint8_t entity_update_sub_tail = 0;
|
||||
static uint8_t entity_update_sub_count = 0;
|
||||
|
||||
static void entity_attr_req_queue_reset(void)
|
||||
{
|
||||
entity_attr_req_head = 0;
|
||||
entity_attr_req_tail = 0;
|
||||
entity_attr_req_count = 0;
|
||||
entity_attr_req_in_flight = false;
|
||||
pending_entity_id = 0;
|
||||
pending_attribute_id = 0;
|
||||
}
|
||||
|
||||
static void entity_update_sub_queue_reset(void)
|
||||
{
|
||||
entity_update_sub_head = 0;
|
||||
entity_update_sub_tail = 0;
|
||||
entity_update_sub_count = 0;
|
||||
entity_update_sub_in_flight = false;
|
||||
}
|
||||
|
||||
static bool entity_attr_req_queue_push(uint8_t entity_id, uint8_t attribute_id)
|
||||
{
|
||||
if (entity_attr_req_count >= ENTITY_ATTR_REQ_QUEUE_LEN) {
|
||||
return false;
|
||||
}
|
||||
|
||||
entity_attr_req_queue[entity_attr_req_tail].entity_id = entity_id;
|
||||
entity_attr_req_queue[entity_attr_req_tail].attribute_id = attribute_id;
|
||||
entity_attr_req_tail = (entity_attr_req_tail + 1) % ENTITY_ATTR_REQ_QUEUE_LEN;
|
||||
entity_attr_req_count++;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool entity_attr_req_queue_pop(entity_attr_req_t *req)
|
||||
{
|
||||
if (!req || entity_attr_req_count == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*req = entity_attr_req_queue[entity_attr_req_head];
|
||||
entity_attr_req_head = (entity_attr_req_head + 1) % ENTITY_ATTR_REQ_QUEUE_LEN;
|
||||
entity_attr_req_count--;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void esp_process_next_entity_attribute_request(void)
|
||||
{
|
||||
entity_attr_req_t req;
|
||||
uint8_t cmd[2];
|
||||
esp_err_t ret;
|
||||
|
||||
if (gl_profile_tab[PROFILE_A_APP_ID].entity_attribute_handle == 0) {
|
||||
ESP_LOGW(BLE_AMS_DEMO_TAG, "Entity Attribute handle not discovered");
|
||||
return;
|
||||
}
|
||||
|
||||
if (entity_attr_req_in_flight || !entity_attr_req_queue_pop(&req)) {
|
||||
return;
|
||||
}
|
||||
|
||||
pending_entity_id = req.entity_id;
|
||||
pending_attribute_id = req.attribute_id;
|
||||
entity_attr_req_in_flight = true;
|
||||
cmd[0] = req.entity_id;
|
||||
cmd[1] = req.attribute_id;
|
||||
|
||||
ret = esp_ble_gattc_write_char(gl_profile_tab[PROFILE_A_APP_ID].gattc_if,
|
||||
gl_profile_tab[PROFILE_A_APP_ID].conn_id,
|
||||
gl_profile_tab[PROFILE_A_APP_ID].entity_attribute_handle,
|
||||
sizeof(cmd),
|
||||
cmd,
|
||||
ESP_GATT_WRITE_TYPE_RSP,
|
||||
ESP_GATT_AUTH_REQ_NONE);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(BLE_AMS_DEMO_TAG, "write entity attribute request failed, error = %x", ret);
|
||||
entity_attr_req_in_flight = false;
|
||||
esp_process_next_entity_attribute_request();
|
||||
}
|
||||
}
|
||||
|
||||
static bool entity_update_sub_queue_push(const uint8_t *cmd, uint8_t cmd_len)
|
||||
{
|
||||
if (entity_update_sub_count >= ENTITY_UPDATE_SUB_QUEUE_LEN ||
|
||||
cmd_len > ENTITY_UPDATE_SUB_CMD_MAX_LEN) {
|
||||
return false;
|
||||
}
|
||||
|
||||
entity_update_sub_queue[entity_update_sub_tail].cmd_len = cmd_len;
|
||||
memcpy(entity_update_sub_queue[entity_update_sub_tail].cmd, cmd, cmd_len);
|
||||
entity_update_sub_tail = (entity_update_sub_tail + 1) % ENTITY_UPDATE_SUB_QUEUE_LEN;
|
||||
entity_update_sub_count++;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool entity_update_sub_queue_pop(entity_update_sub_req_t *req)
|
||||
{
|
||||
if (!req || entity_update_sub_count == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*req = entity_update_sub_queue[entity_update_sub_head];
|
||||
entity_update_sub_head = (entity_update_sub_head + 1) % ENTITY_UPDATE_SUB_QUEUE_LEN;
|
||||
entity_update_sub_count--;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void esp_process_next_entity_update_subscribe(void)
|
||||
{
|
||||
entity_update_sub_req_t req;
|
||||
esp_err_t ret;
|
||||
|
||||
if (entity_update_sub_in_flight || !entity_update_sub_queue_pop(&req)) {
|
||||
return;
|
||||
}
|
||||
|
||||
entity_update_sub_in_flight = true;
|
||||
ESP_LOGI(BLE_AMS_DEMO_TAG, "Subscribe %s entity updates", EntityID_to_String(req.cmd[0]));
|
||||
|
||||
ret = esp_ble_gattc_write_char(gl_profile_tab[PROFILE_A_APP_ID].gattc_if,
|
||||
gl_profile_tab[PROFILE_A_APP_ID].conn_id,
|
||||
gl_profile_tab[PROFILE_A_APP_ID].entity_update_handle,
|
||||
req.cmd_len,
|
||||
req.cmd,
|
||||
ESP_GATT_WRITE_TYPE_RSP,
|
||||
ESP_GATT_AUTH_REQ_NONE);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(BLE_AMS_DEMO_TAG, "write entity update subscribe request failed, error = %x", ret);
|
||||
entity_update_sub_in_flight = false;
|
||||
esp_process_next_entity_update_subscribe();
|
||||
}
|
||||
}
|
||||
|
||||
static void esp_subscribe_entity_updates(void)
|
||||
{
|
||||
const uint8_t track_cmd[] = {
|
||||
EntityIDTrack,
|
||||
TrackAttributeIDArtist,
|
||||
TrackAttributeIDAlbum,
|
||||
TrackAttributeIDTitle,
|
||||
TrackAttributeIDDuration,
|
||||
};
|
||||
const uint8_t player_cmd[] = {
|
||||
EntityIDPlayer,
|
||||
PlayerAttributeIDName,
|
||||
PlayerAttributeIDPlaybackInfo,
|
||||
PlayerAttributeIDVolume,
|
||||
};
|
||||
const uint8_t queue_cmd[] = {
|
||||
EntityIDQueue,
|
||||
QueueAttributeIDIndex,
|
||||
QueueAttributeIDCount,
|
||||
QueueAttributeIDShuffleMode,
|
||||
QueueAttributeIDRepeatMode,
|
||||
};
|
||||
|
||||
entity_update_sub_queue_reset();
|
||||
|
||||
if (!entity_update_sub_queue_push(track_cmd, sizeof(track_cmd)) ||
|
||||
!entity_update_sub_queue_push(player_cmd, sizeof(player_cmd)) ||
|
||||
!entity_update_sub_queue_push(queue_cmd, sizeof(queue_cmd))) {
|
||||
ESP_LOGW(BLE_AMS_DEMO_TAG, "Failed to queue entity update subscribe requests");
|
||||
return;
|
||||
}
|
||||
|
||||
esp_process_next_entity_update_subscribe();
|
||||
}
|
||||
|
||||
esp_err_t esp_send_remote_command(uint8_t command_id)
|
||||
{
|
||||
if (gl_profile_tab[PROFILE_A_APP_ID].remote_command_handle == 0) {
|
||||
ESP_LOGW(BLE_AMS_DEMO_TAG, "Remote Command handle not discovered");
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
esp_err_t ret = esp_ble_gattc_write_char(gl_profile_tab[PROFILE_A_APP_ID].gattc_if,
|
||||
gl_profile_tab[PROFILE_A_APP_ID].conn_id,
|
||||
gl_profile_tab[PROFILE_A_APP_ID].remote_command_handle,
|
||||
1,
|
||||
&command_id,
|
||||
ESP_GATT_WRITE_TYPE_RSP,
|
||||
ESP_GATT_AUTH_REQ_NONE);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(BLE_AMS_DEMO_TAG, "write remote command failed, error = %x", ret);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void esp_request_entity_attribute(uint8_t entity_id, uint8_t attribute_id)
|
||||
{
|
||||
if (!entity_attr_req_queue_push(entity_id, attribute_id)) {
|
||||
ESP_LOGW(BLE_AMS_DEMO_TAG,
|
||||
"Entity Attribute request dropped (entity=%u attr=%u): queue full (max=%u)",
|
||||
entity_id,
|
||||
attribute_id,
|
||||
ENTITY_ATTR_REQ_QUEUE_LEN);
|
||||
return;
|
||||
}
|
||||
|
||||
esp_process_next_entity_attribute_request();
|
||||
}
|
||||
|
||||
static bool ams_find_cccd_handle(esp_gatt_if_t gattc_if, uint16_t char_handle,
|
||||
uint16_t *cccd_handle, const char *char_name)
|
||||
{
|
||||
esp_gattc_descr_elem_t descr;
|
||||
uint16_t count = 1;
|
||||
|
||||
esp_gatt_status_t status = esp_ble_gattc_get_descr_by_char_handle(gattc_if,
|
||||
gl_profile_tab[PROFILE_A_APP_ID].conn_id,
|
||||
char_handle,
|
||||
cccd_uuid,
|
||||
&descr,
|
||||
&count);
|
||||
if (status != ESP_GATT_OK || count == 0) {
|
||||
ESP_LOGE(BLE_AMS_DEMO_TAG, "%s CCCD not found (char 0x%04x, status 0x%x)",
|
||||
char_name, char_handle, status);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (descr.uuid.len != ESP_UUID_LEN_16 ||
|
||||
descr.uuid.uuid.uuid16 != ESP_GATT_UUID_CHAR_CLIENT_CONFIG) {
|
||||
ESP_LOGE(BLE_AMS_DEMO_TAG, "%s unexpected descriptor UUID (char 0x%04x)", char_name, char_handle);
|
||||
return false;
|
||||
}
|
||||
|
||||
*cccd_handle = descr.handle;
|
||||
ESP_LOGI(BLE_AMS_DEMO_TAG, "Found %s CCCD handle 0x%04x (char 0x%04x)",
|
||||
char_name, *cccd_handle, char_handle);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void ams_write_cccd(esp_gatt_if_t gattc_if, uint16_t cccd_handle, uint16_t char_handle)
|
||||
{
|
||||
uint8_t notify_en[2] = {0x01, 0x00};
|
||||
esp_err_t ret;
|
||||
|
||||
pending_cccd_char_handle = char_handle;
|
||||
ret = esp_ble_gattc_write_char_descr(gattc_if,
|
||||
gl_profile_tab[PROFILE_A_APP_ID].conn_id,
|
||||
cccd_handle,
|
||||
sizeof(notify_en),
|
||||
notify_en,
|
||||
ESP_GATT_WRITE_TYPE_RSP,
|
||||
ESP_GATT_AUTH_REQ_NONE);
|
||||
if (ret != ESP_OK) {
|
||||
pending_cccd_char_handle = 0;
|
||||
ESP_LOGE(BLE_AMS_DEMO_TAG, "write cccd failed, error = %x", ret);
|
||||
if (char_handle == gl_profile_tab[PROFILE_A_APP_ID].remote_command_handle) {
|
||||
ESP_LOGW(BLE_AMS_DEMO_TAG, "Remote Command CCCD write failed, continue Entity Update subscribe");
|
||||
ams_schedule_entity_update_notify(gattc_if);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void ams_start_remote_command_notify(esp_gatt_if_t gattc_if)
|
||||
{
|
||||
struct gattc_profile_inst *profile = &gl_profile_tab[PROFILE_A_APP_ID];
|
||||
esp_err_t ret;
|
||||
|
||||
if (profile->remote_command_handle == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ams_find_cccd_handle(gattc_if, profile->remote_command_handle,
|
||||
&remote_command_cccd_handle, "Remote Command")) {
|
||||
ams_schedule_entity_update_notify(gattc_if);
|
||||
return;
|
||||
}
|
||||
|
||||
ret = esp_ble_gattc_register_for_notify(gattc_if, profile->remote_bda, profile->remote_command_handle);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(BLE_AMS_DEMO_TAG, "register Remote Command notify failed, error = %x", ret);
|
||||
ams_schedule_entity_update_notify(gattc_if);
|
||||
}
|
||||
}
|
||||
|
||||
static void ams_start_entity_update_notify(esp_gatt_if_t gattc_if)
|
||||
{
|
||||
struct gattc_profile_inst *profile = &gl_profile_tab[PROFILE_A_APP_ID];
|
||||
esp_err_t ret;
|
||||
|
||||
if (profile->entity_update_handle == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ams_find_cccd_handle(gattc_if, profile->entity_update_handle,
|
||||
&entity_update_cccd_handle, "Entity Update")) {
|
||||
return;
|
||||
}
|
||||
|
||||
ret = esp_ble_gattc_register_for_notify(gattc_if, profile->remote_bda, profile->entity_update_handle);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(BLE_AMS_DEMO_TAG, "register Entity Update notify failed, error = %x", ret);
|
||||
}
|
||||
}
|
||||
|
||||
static void ams_entity_update_cccd_delay_timer_cb(void *arg)
|
||||
{
|
||||
esp_gatt_if_t gattc_if = pending_entity_update_gattc_if;
|
||||
|
||||
pending_entity_update_gattc_if = ESP_GATT_IF_NONE;
|
||||
if (gattc_if == ESP_GATT_IF_NONE) {
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGI(BLE_AMS_DEMO_TAG, "Starting Entity Update CCCD subscribe after delay");
|
||||
ams_start_entity_update_notify(gattc_if);
|
||||
}
|
||||
|
||||
static void ams_schedule_entity_update_notify(esp_gatt_if_t gattc_if)
|
||||
{
|
||||
pending_entity_update_gattc_if = gattc_if;
|
||||
esp_timer_stop(entity_update_cccd_delay_timer);
|
||||
esp_timer_start_once(entity_update_cccd_delay_timer, AMS_ENTITY_UPDATE_CCCD_DELAY_US);
|
||||
}
|
||||
|
||||
static void ble_ams_start_adv_config(void)
|
||||
{
|
||||
esp_err_t ret = esp_ble_gap_config_adv_data(&adv_config);
|
||||
if (ret) {
|
||||
ESP_LOGE(BLE_AMS_DEMO_TAG, "config adv data failed, error code = %x", ret);
|
||||
} else {
|
||||
adv_config_done |= ADV_CONFIG_FLAG;
|
||||
}
|
||||
|
||||
ret = esp_ble_gap_config_adv_data(&scan_rsp_config);
|
||||
if (ret) {
|
||||
ESP_LOGE(BLE_AMS_DEMO_TAG, "config scan rsp data failed, error code = %x", ret);
|
||||
} else {
|
||||
adv_config_done |= SCAN_RSP_CONFIG_FLAG;
|
||||
}
|
||||
}
|
||||
|
||||
static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param)
|
||||
{
|
||||
switch (event) {
|
||||
case ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT:
|
||||
adv_config_done &= (~SCAN_RSP_CONFIG_FLAG);
|
||||
if (adv_config_done == 0) {
|
||||
esp_ble_gap_start_advertising(&adv_params);
|
||||
}
|
||||
break;
|
||||
case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT:
|
||||
adv_config_done &= (~ADV_CONFIG_FLAG);
|
||||
if (adv_config_done == 0) {
|
||||
esp_ble_gap_start_advertising(&adv_params);
|
||||
}
|
||||
break;
|
||||
case ESP_GAP_BLE_ADV_START_COMPLETE_EVT:
|
||||
if (param->adv_start_cmpl.status != ESP_BT_STATUS_SUCCESS) {
|
||||
ESP_LOGE(BLE_AMS_DEMO_TAG, "advertising start failed, error status = %x", param->adv_start_cmpl.status);
|
||||
break;
|
||||
}
|
||||
ESP_LOGI(BLE_AMS_DEMO_TAG, "advertising start success");
|
||||
break;
|
||||
case ESP_GAP_BLE_OOB_REQ_EVT: {
|
||||
uint8_t tk[16] = {1};
|
||||
esp_ble_oob_req_reply(param->ble_security.ble_req.bd_addr, tk, sizeof(tk));
|
||||
break;
|
||||
}
|
||||
case ESP_GAP_BLE_NC_REQ_EVT:
|
||||
esp_ble_confirm_reply(param->ble_security.ble_req.bd_addr, true);
|
||||
ESP_LOGI(BLE_AMS_DEMO_TAG, "ESP_GAP_BLE_NC_REQ_EVT, the passkey Notify number:%" PRIu32,
|
||||
param->ble_security.key_notif.passkey);
|
||||
break;
|
||||
case ESP_GAP_BLE_SEC_REQ_EVT:
|
||||
esp_ble_gap_security_rsp(param->ble_security.ble_req.bd_addr, true);
|
||||
break;
|
||||
case ESP_GAP_BLE_PASSKEY_NOTIF_EVT:
|
||||
ESP_LOGI(BLE_AMS_DEMO_TAG, "The passkey Notify number:%06" PRIu32, param->ble_security.key_notif.passkey);
|
||||
break;
|
||||
case ESP_GAP_BLE_AUTH_CMPL_EVT:
|
||||
ESP_LOG_BUFFER_HEX("addr", param->ble_security.auth_cmpl.bd_addr, ESP_BD_ADDR_LEN);
|
||||
ESP_LOGI(BLE_AMS_DEMO_TAG, "pair status = %s", param->ble_security.auth_cmpl.success ? "success" : "fail");
|
||||
if (!param->ble_security.auth_cmpl.success) {
|
||||
ESP_LOGI(BLE_AMS_DEMO_TAG, "fail reason = 0x%x", param->ble_security.auth_cmpl.fail_reason);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param)
|
||||
{
|
||||
switch (event) {
|
||||
case ESP_GATTC_REG_EVT:
|
||||
ESP_LOGI(BLE_AMS_DEMO_TAG, "REG_EVT");
|
||||
esp_ble_gap_set_device_name(EXAMPLE_DEVICE_NAME);
|
||||
esp_ble_gap_config_local_icon(ESP_BLE_APPEARANCE_GENERIC_WATCH);
|
||||
ble_ams_start_adv_config();
|
||||
break;
|
||||
case ESP_GATTC_OPEN_EVT:
|
||||
if (param->open.status != ESP_GATT_OK) {
|
||||
ESP_LOGE(BLE_AMS_DEMO_TAG, "open failed, error status = %x", param->open.status);
|
||||
break;
|
||||
}
|
||||
ESP_LOGI(BLE_AMS_DEMO_TAG, "ESP_GATTC_OPEN_EVT");
|
||||
gl_profile_tab[PROFILE_A_APP_ID].conn_id = param->open.conn_id;
|
||||
esp_ble_set_encryption(param->open.remote_bda, ESP_BLE_SEC_ENCRYPT_MITM);
|
||||
esp_err_t mtu_ret = esp_ble_gattc_send_mtu_req(gattc_if, param->open.conn_id);
|
||||
if (mtu_ret) {
|
||||
ESP_LOGE(BLE_AMS_DEMO_TAG, "config MTU error, error code = %x", mtu_ret);
|
||||
}
|
||||
break;
|
||||
case ESP_GATTC_CFG_MTU_EVT:
|
||||
if (param->cfg_mtu.status != ESP_GATT_OK) {
|
||||
ESP_LOGE(BLE_AMS_DEMO_TAG, "config mtu failed, error status = %x", param->cfg_mtu.status);
|
||||
}
|
||||
ESP_LOGI(BLE_AMS_DEMO_TAG, "ESP_GATTC_CFG_MTU_EVT, Status %d, MTU %d, conn_id %d",
|
||||
param->cfg_mtu.status, param->cfg_mtu.mtu, param->cfg_mtu.conn_id);
|
||||
gl_profile_tab[PROFILE_A_APP_ID].MTU_size = param->cfg_mtu.mtu;
|
||||
memcpy(apple_ms_uuid.uuid.uuid128, Apple_MS_UUID, 16);
|
||||
esp_ble_gattc_search_service(gl_profile_tab[PROFILE_A_APP_ID].gattc_if,
|
||||
gl_profile_tab[PROFILE_A_APP_ID].conn_id,
|
||||
&apple_ms_uuid);
|
||||
break;
|
||||
case ESP_GATTC_SEARCH_RES_EVT:
|
||||
if (param->search_res.srvc_id.uuid.len == ESP_UUID_LEN_128) {
|
||||
gl_profile_tab[PROFILE_A_APP_ID].service_start_handle = param->search_res.start_handle;
|
||||
gl_profile_tab[PROFILE_A_APP_ID].service_end_handle = param->search_res.end_handle;
|
||||
get_service = true;
|
||||
}
|
||||
break;
|
||||
case ESP_GATTC_SEARCH_CMPL_EVT:
|
||||
if (param->search_cmpl.status != ESP_GATT_OK) {
|
||||
ESP_LOGE(BLE_AMS_DEMO_TAG, "search service failed, error status = %x", param->search_cmpl.status);
|
||||
break;
|
||||
}
|
||||
ESP_LOGI(BLE_AMS_DEMO_TAG, "ESP_GATTC_SEARCH_CMPL_EVT");
|
||||
if (!get_service) {
|
||||
ESP_LOGE(BLE_AMS_DEMO_TAG, "No Apple Media Service found");
|
||||
break;
|
||||
}
|
||||
|
||||
{
|
||||
uint16_t count = 0;
|
||||
uint16_t offset = 0;
|
||||
esp_gatt_status_t ret_status = esp_ble_gattc_get_attr_count(gattc_if,
|
||||
gl_profile_tab[PROFILE_A_APP_ID].conn_id,
|
||||
ESP_GATT_DB_CHARACTERISTIC,
|
||||
gl_profile_tab[PROFILE_A_APP_ID].service_start_handle,
|
||||
gl_profile_tab[PROFILE_A_APP_ID].service_end_handle,
|
||||
INVALID_HANDLE,
|
||||
&count);
|
||||
if (ret_status != ESP_GATT_OK || count == 0) {
|
||||
ESP_LOGE(BLE_AMS_DEMO_TAG, "esp_ble_gattc_get_attr_count error");
|
||||
break;
|
||||
}
|
||||
|
||||
char_elem_result = (esp_gattc_char_elem_t *)malloc(sizeof(esp_gattc_char_elem_t) * count);
|
||||
if (!char_elem_result) {
|
||||
ESP_LOGE(BLE_AMS_DEMO_TAG, "gattc no mem");
|
||||
break;
|
||||
}
|
||||
|
||||
memset(char_elem_result, 0xff, sizeof(esp_gattc_char_elem_t) * count);
|
||||
ret_status = esp_ble_gattc_get_all_char(gattc_if,
|
||||
gl_profile_tab[PROFILE_A_APP_ID].conn_id,
|
||||
gl_profile_tab[PROFILE_A_APP_ID].service_start_handle,
|
||||
gl_profile_tab[PROFILE_A_APP_ID].service_end_handle,
|
||||
char_elem_result,
|
||||
&count,
|
||||
offset);
|
||||
if (ret_status != ESP_GATT_OK) {
|
||||
ESP_LOGE(BLE_AMS_DEMO_TAG, "esp_ble_gattc_get_all_char error");
|
||||
free(char_elem_result);
|
||||
char_elem_result = NULL;
|
||||
break;
|
||||
}
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
if (char_elem_result[i].uuid.len != ESP_UUID_LEN_128) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((char_elem_result[i].properties & ESP_GATT_CHAR_PROP_BIT_NOTIFY) &&
|
||||
memcmp(char_elem_result[i].uuid.uuid.uuid128, remote_command_uuid, 16) == 0) {
|
||||
gl_profile_tab[PROFILE_A_APP_ID].remote_command_handle = char_elem_result[i].char_handle;
|
||||
ESP_LOGI(BLE_AMS_DEMO_TAG, "Found Remote Command characteristic");
|
||||
} else if ((char_elem_result[i].properties & ESP_GATT_CHAR_PROP_BIT_NOTIFY) &&
|
||||
memcmp(char_elem_result[i].uuid.uuid.uuid128, entity_update_uuid, 16) == 0) {
|
||||
gl_profile_tab[PROFILE_A_APP_ID].entity_update_handle = char_elem_result[i].char_handle;
|
||||
ESP_LOGI(BLE_AMS_DEMO_TAG, "Found Entity Update characteristic");
|
||||
} else if (memcmp(char_elem_result[i].uuid.uuid.uuid128, entity_attribute_uuid, 16) == 0) {
|
||||
gl_profile_tab[PROFILE_A_APP_ID].entity_attribute_handle = char_elem_result[i].char_handle;
|
||||
ESP_LOGI(BLE_AMS_DEMO_TAG, "Found Entity Attribute characteristic");
|
||||
}
|
||||
}
|
||||
|
||||
if (gl_profile_tab[PROFILE_A_APP_ID].remote_command_handle != 0) {
|
||||
ams_start_remote_command_notify(gattc_if);
|
||||
} else if (gl_profile_tab[PROFILE_A_APP_ID].entity_update_handle != 0) {
|
||||
ams_start_entity_update_notify(gattc_if);
|
||||
}
|
||||
|
||||
free(char_elem_result);
|
||||
char_elem_result = NULL;
|
||||
}
|
||||
break;
|
||||
case ESP_GATTC_REG_FOR_NOTIFY_EVT:
|
||||
if (param->reg_for_notify.status != ESP_GATT_OK) {
|
||||
ESP_LOGI(BLE_AMS_DEMO_TAG, "ESP_GATTC_REG_FOR_NOTIFY_EVT status %d", param->reg_for_notify.status);
|
||||
if (param->reg_for_notify.handle == gl_profile_tab[PROFILE_A_APP_ID].remote_command_handle) {
|
||||
ams_schedule_entity_update_notify(gattc_if);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (param->reg_for_notify.handle == gl_profile_tab[PROFILE_A_APP_ID].remote_command_handle &&
|
||||
remote_command_cccd_handle != 0) {
|
||||
ams_write_cccd(gattc_if, remote_command_cccd_handle,
|
||||
gl_profile_tab[PROFILE_A_APP_ID].remote_command_handle);
|
||||
} else if (param->reg_for_notify.handle == gl_profile_tab[PROFILE_A_APP_ID].entity_update_handle &&
|
||||
entity_update_cccd_handle != 0) {
|
||||
ams_write_cccd(gattc_if, entity_update_cccd_handle,
|
||||
gl_profile_tab[PROFILE_A_APP_ID].entity_update_handle);
|
||||
}
|
||||
break;
|
||||
case ESP_GATTC_WRITE_DESCR_EVT:
|
||||
if (param->write.status != ESP_GATT_OK) {
|
||||
bool remote_command_pending = (pending_cccd_char_handle != 0 &&
|
||||
pending_cccd_char_handle == gl_profile_tab[PROFILE_A_APP_ID].remote_command_handle);
|
||||
ESP_LOGE(BLE_AMS_DEMO_TAG, "write descr failed, error status = %x", param->write.status);
|
||||
pending_cccd_char_handle = 0;
|
||||
if (remote_command_pending) {
|
||||
ESP_LOGW(BLE_AMS_DEMO_TAG, "Remote Command CCCD callback failed, continue Entity Update subscribe");
|
||||
ams_schedule_entity_update_notify(gattc_if);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (pending_cccd_char_handle != 0 &&
|
||||
pending_cccd_char_handle == gl_profile_tab[PROFILE_A_APP_ID].remote_command_handle) {
|
||||
pending_cccd_char_handle = 0;
|
||||
remote_command_cccd_handle = 0;
|
||||
ESP_LOGI(BLE_AMS_DEMO_TAG, "Remote Command notifications enabled, delay 500 ms before Entity Update CCCD");
|
||||
ams_schedule_entity_update_notify(gattc_if);
|
||||
} else if (pending_cccd_char_handle != 0 &&
|
||||
pending_cccd_char_handle == gl_profile_tab[PROFILE_A_APP_ID].entity_update_handle &&
|
||||
!entity_update_notify_enabled) {
|
||||
pending_cccd_char_handle = 0;
|
||||
entity_update_cccd_handle = 0;
|
||||
entity_update_notify_enabled = true;
|
||||
ESP_LOGI(BLE_AMS_DEMO_TAG, "Entity Update notifications enabled");
|
||||
esp_subscribe_entity_updates();
|
||||
} else {
|
||||
pending_cccd_char_handle = 0;
|
||||
}
|
||||
break;
|
||||
case ESP_GATTC_NOTIFY_EVT:
|
||||
if (param->notify.handle == gl_profile_tab[PROFILE_A_APP_ID].remote_command_handle) {
|
||||
esp_receive_remote_command_notification(param->notify.value, param->notify.value_len);
|
||||
} else if (param->notify.handle == gl_profile_tab[PROFILE_A_APP_ID].entity_update_handle) {
|
||||
if (!param->notify.value || param->notify.value_len < 3) {
|
||||
ESP_LOGW(BLE_AMS_DEMO_TAG, "Entity update notification too short (%u)", param->notify.value_len);
|
||||
break;
|
||||
}
|
||||
esp_receive_entity_update(param->notify.value, param->notify.value_len);
|
||||
if (param->notify.value[2] & EntityUpdateFlagTruncated) {
|
||||
esp_request_entity_attribute(param->notify.value[0], param->notify.value[1]);
|
||||
}
|
||||
} else {
|
||||
ESP_LOGI(BLE_AMS_DEMO_TAG, "Unknown notify handle 0x%04x", param->notify.handle);
|
||||
}
|
||||
break;
|
||||
case ESP_GATTC_WRITE_CHAR_EVT:
|
||||
if (param->write.status != ESP_GATT_OK) {
|
||||
ESP_LOGE(BLE_AMS_DEMO_TAG, "write char error, status = 0x%x (%s)",
|
||||
param->write.status, Errcode_to_String(param->write.status));
|
||||
if (param->write.handle == gl_profile_tab[PROFILE_A_APP_ID].entity_attribute_handle) {
|
||||
entity_attr_req_in_flight = false;
|
||||
esp_process_next_entity_attribute_request();
|
||||
} else if (param->write.handle == gl_profile_tab[PROFILE_A_APP_ID].entity_update_handle) {
|
||||
entity_update_sub_in_flight = false;
|
||||
esp_process_next_entity_update_subscribe();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (param->write.handle == gl_profile_tab[PROFILE_A_APP_ID].entity_update_handle) {
|
||||
entity_update_sub_in_flight = false;
|
||||
esp_process_next_entity_update_subscribe();
|
||||
break;
|
||||
}
|
||||
|
||||
if (param->write.handle == gl_profile_tab[PROFILE_A_APP_ID].entity_attribute_handle) {
|
||||
esp_err_t ret = esp_ble_gattc_read_char(gattc_if,
|
||||
gl_profile_tab[PROFILE_A_APP_ID].conn_id,
|
||||
gl_profile_tab[PROFILE_A_APP_ID].entity_attribute_handle,
|
||||
ESP_GATT_AUTH_REQ_NONE);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(BLE_AMS_DEMO_TAG, "read char request failed, error = %x", ret);
|
||||
entity_attr_req_in_flight = false;
|
||||
esp_process_next_entity_attribute_request();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ESP_GATTC_READ_CHAR_EVT:
|
||||
if (param->read.status != ESP_GATT_OK) {
|
||||
ESP_LOGE(BLE_AMS_DEMO_TAG, "read char failed, error status = %x", param->read.status);
|
||||
}
|
||||
if (param->read.handle == gl_profile_tab[PROFILE_A_APP_ID].entity_attribute_handle) {
|
||||
if (param->read.status == ESP_GATT_OK) {
|
||||
esp_receive_entity_attribute_value(pending_entity_id,
|
||||
pending_attribute_id,
|
||||
param->read.value,
|
||||
param->read.value_len);
|
||||
}
|
||||
entity_attr_req_in_flight = false;
|
||||
esp_process_next_entity_attribute_request();
|
||||
}
|
||||
break;
|
||||
case ESP_GATTC_DISCONNECT_EVT:
|
||||
ESP_LOGI(BLE_AMS_DEMO_TAG, "ESP_GATTC_DISCONNECT_EVT, reason = 0x%x", param->disconnect.reason);
|
||||
get_service = false;
|
||||
entity_update_notify_enabled = false;
|
||||
pending_cccd_char_handle = 0;
|
||||
remote_command_cccd_handle = 0;
|
||||
entity_update_cccd_handle = 0;
|
||||
pending_entity_update_gattc_if = ESP_GATT_IF_NONE;
|
||||
entity_attr_req_queue_reset();
|
||||
entity_update_sub_queue_reset();
|
||||
esp_timer_stop(entity_update_cccd_delay_timer);
|
||||
gl_profile_tab[PROFILE_A_APP_ID].remote_command_handle = 0;
|
||||
gl_profile_tab[PROFILE_A_APP_ID].entity_update_handle = 0;
|
||||
gl_profile_tab[PROFILE_A_APP_ID].entity_attribute_handle = 0;
|
||||
esp_ble_gap_start_advertising(&adv_params);
|
||||
break;
|
||||
case ESP_GATTC_CONNECT_EVT:
|
||||
memcpy(gl_profile_tab[PROFILE_A_APP_ID].remote_bda, param->connect.remote_bda, ESP_BD_ADDR_LEN);
|
||||
esp_ble_gatt_creat_conn_params_t creat_conn_params = {0};
|
||||
memcpy(&creat_conn_params.remote_bda, gl_profile_tab[PROFILE_A_APP_ID].remote_bda, ESP_BD_ADDR_LEN);
|
||||
creat_conn_params.remote_addr_type = BLE_ADDR_TYPE_RANDOM;
|
||||
creat_conn_params.own_addr_type = BLE_ADDR_TYPE_PUBLIC;
|
||||
creat_conn_params.is_direct = true;
|
||||
creat_conn_params.is_aux = false;
|
||||
creat_conn_params.phy_mask = 0x0;
|
||||
esp_ble_gattc_enh_open(gl_profile_tab[PROFILE_A_APP_ID].gattc_if, &creat_conn_params);
|
||||
break;
|
||||
case ESP_GATTC_SRVC_CHG_EVT:
|
||||
ESP_LOGI(BLE_AMS_DEMO_TAG, "ESP_GATTC_SRVC_CHG_EVT");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void esp_gattc_cb(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param)
|
||||
{
|
||||
if (event == ESP_GATTC_REG_EVT) {
|
||||
if (param->reg.status == ESP_GATT_OK) {
|
||||
gl_profile_tab[param->reg.app_id].gattc_if = gattc_if;
|
||||
} else {
|
||||
ESP_LOGI(BLE_AMS_DEMO_TAG, "Reg app failed, app_id %04x, status %d",
|
||||
param->reg.app_id, param->reg.status);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
for (int idx = 0; idx < PROFILE_NUM; idx++) {
|
||||
if (gattc_if == ESP_GATT_IF_NONE || gattc_if == gl_profile_tab[idx].gattc_if) {
|
||||
if (gl_profile_tab[idx].gattc_cb) {
|
||||
gl_profile_tab[idx].gattc_cb(event, gattc_if, param);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
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);
|
||||
|
||||
ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT));
|
||||
|
||||
esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
|
||||
ESP_ERROR_CHECK(esp_bt_controller_init(&bt_cfg));
|
||||
ESP_ERROR_CHECK(esp_bt_controller_enable(ESP_BT_MODE_BLE));
|
||||
|
||||
ESP_LOGI(BLE_AMS_DEMO_TAG, "%s init bluetooth", __func__);
|
||||
|
||||
esp_bluedroid_config_t cfg = BT_BLUEDROID_INIT_CONFIG_DEFAULT();
|
||||
ESP_ERROR_CHECK(esp_bluedroid_init_with_cfg(&cfg));
|
||||
ESP_ERROR_CHECK(esp_bluedroid_enable());
|
||||
ESP_ERROR_CHECK(esp_ble_gattc_register_callback(esp_gattc_cb));
|
||||
ESP_ERROR_CHECK(esp_ble_gap_register_callback(gap_event_handler));
|
||||
ESP_ERROR_CHECK(esp_ble_gattc_app_register(PROFILE_A_APP_ID));
|
||||
ESP_ERROR_CHECK(esp_ble_gatt_set_local_mtu(500));
|
||||
|
||||
const esp_timer_create_args_t entity_update_cccd_delay_timer_args = {
|
||||
.callback = ams_entity_update_cccd_delay_timer_cb,
|
||||
.name = "ams_eu_cccd",
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_timer_create(&entity_update_cccd_delay_timer_args, &entity_update_cccd_delay_timer));
|
||||
|
||||
esp_ble_auth_req_t auth_req = ESP_LE_AUTH_REQ_SC_MITM_BOND;
|
||||
esp_ble_io_cap_t iocap = ESP_IO_CAP_NONE;
|
||||
uint8_t key_size = 16;
|
||||
uint8_t init_key = ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK;
|
||||
uint8_t rsp_key = ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK;
|
||||
uint32_t passkey = 123456;
|
||||
uint8_t auth_option = ESP_BLE_ONLY_ACCEPT_SPECIFIED_AUTH_DISABLE;
|
||||
uint8_t oob_support = ESP_BLE_OOB_DISABLE;
|
||||
|
||||
esp_ble_gap_set_security_param(ESP_BLE_SM_SET_STATIC_PASSKEY, &passkey, sizeof(uint32_t));
|
||||
esp_ble_gap_set_security_param(ESP_BLE_SM_AUTHEN_REQ_MODE, &auth_req, sizeof(uint8_t));
|
||||
esp_ble_gap_set_security_param(ESP_BLE_SM_IOCAP_MODE, &iocap, sizeof(uint8_t));
|
||||
esp_ble_gap_set_security_param(ESP_BLE_SM_MAX_KEY_SIZE, &key_size, sizeof(uint8_t));
|
||||
esp_ble_gap_set_security_param(ESP_BLE_SM_ONLY_ACCEPT_SPECIFIED_SEC_AUTH, &auth_option, sizeof(uint8_t));
|
||||
esp_ble_gap_set_security_param(ESP_BLE_SM_OOB_SUPPORT, &oob_support, sizeof(uint8_t));
|
||||
esp_ble_gap_set_security_param(ESP_BLE_SM_SET_INIT_KEY, &init_key, sizeof(uint8_t));
|
||||
esp_ble_gap_set_security_param(ESP_BLE_SM_SET_RSP_KEY, &rsp_key, sizeof(uint8_t));
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
# This file was generated using idf.py save-defconfig. It can be edited manually.
|
||||
# Espressif IoT Development Framework (ESP-IDF) Project Minimal Configuration
|
||||
#
|
||||
CONFIG_BT_ENABLED=y
|
||||
CONFIG_BT_BLE_50_FEATURES_SUPPORTED=n
|
||||
CONFIG_BT_BLE_42_FEATURES_SUPPORTED=y
|
||||
# CONFIG_BT_LE_50_FEATURE_SUPPORT is not used on ESP32, ESP32-C3 and ESP32-S3.
|
||||
CONFIG_BT_LE_50_FEATURE_SUPPORT=n
|
||||
|
||||
CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE=y
|
||||
# Disable unused Bluedroid host features for AMS example
|
||||
# (advertises as peripheral and acts as GATT client to AMS service)
|
||||
# CONFIG_BT_BLE_42_SCAN_EN is not set
|
||||
# CONFIG_BT_BLE_42_DTM_TEST_EN is not set
|
||||
@@ -0,0 +1,9 @@
|
||||
# This file was generated using idf.py save-defconfig. It can be edited manually.
|
||||
# Espressif IoT Development Framework (ESP-IDF) Project Minimal Configuration
|
||||
#
|
||||
CONFIG_IDF_TARGET="esp32c2"
|
||||
CONFIG_BT_ENABLED=y
|
||||
# CONFIG_BT_BLE_50_FEATURES_SUPPORTED is not set
|
||||
CONFIG_BT_BLE_42_FEATURES_SUPPORTED=y
|
||||
# CONFIG_BT_LE_50_FEATURE_SUPPORT is not set
|
||||
CONFIG_BT_LE_HCI_EVT_BUF_SIZE=257
|
||||
@@ -0,0 +1,7 @@
|
||||
# This file was generated using idf.py save-defconfig. It can be edited manually.
|
||||
# Espressif IoT Development Framework (ESP-IDF) Project Minimal Configuration
|
||||
#
|
||||
CONFIG_IDF_TARGET="esp32c3"
|
||||
CONFIG_BT_ENABLED=y
|
||||
# CONFIG_BT_BLE_50_FEATURES_SUPPORTED is not set
|
||||
CONFIG_BT_BLE_42_FEATURES_SUPPORTED=y
|
||||
@@ -0,0 +1,7 @@
|
||||
# This file was generated using idf.py save-defconfig. It can be edited manually.
|
||||
# Espressif IoT Development Framework (ESP-IDF) Project Minimal Configuration
|
||||
#
|
||||
CONFIG_IDF_TARGET="esp32s3"
|
||||
CONFIG_BT_ENABLED=y
|
||||
# CONFIG_BT_BLE_50_FEATURES_SUPPORTED is not set
|
||||
CONFIG_BT_BLE_42_FEATURES_SUPPORTED=y
|
||||
Reference in New Issue
Block a user