mirror of
https://gitee.com/beecue/fastbee.git
synced 2025-12-20 01:45:55 +08:00
添加智能灯固件代码
This commit is contained in:
@@ -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_ancs)
|
||||
@@ -0,0 +1,10 @@
|
||||
#
|
||||
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
|
||||
# project subdirectory.
|
||||
#
|
||||
|
||||
PROJECT_NAME := ble_ancs
|
||||
|
||||
COMPONENT_ADD_INCLUDEDIRS := components/include
|
||||
|
||||
include $(IDF_PATH)/make/project.mk
|
||||
@@ -0,0 +1,21 @@
|
||||
| Supported Targets | ESP32 |
|
||||
| ----------------- | ----- |
|
||||
|
||||
ESP-IDF BLE 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.
|
||||
|
||||
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.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
idf_component_register(SRCS "ble_ancs_demo.c" "ble_ancs.c"
|
||||
INCLUDE_DIRS ".")
|
||||
@@ -0,0 +1,228 @@
|
||||
// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "esp_log.h"
|
||||
#include "ble_ancs.h"
|
||||
|
||||
#define BLE_ANCS_TAG "BLE_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 user’s email inbox, and a new
|
||||
email is pushed to the user’s 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 esp_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(BLE_ANCS_TAG, "EventID:%s EventFlags:0x%x CategoryID:%s CategoryCount:%d NotificationUID:%d", EventIDS, EventFlags, Cidstr, CategoryCount, NotificationUID);
|
||||
}
|
||||
|
||||
void esp_receive_apple_data_source(uint8_t *message, uint16_t message_len)
|
||||
{
|
||||
//esp_log_buffer_hex("data source", message, 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(BLE_ANCS_TAG, "recevice Notification Attributes response Command_id %d NotificationUID %d", 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(BLE_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(BLE_ANCS_TAG, "recevice APP Attributes response");
|
||||
break;
|
||||
case CommandIDPerformNotificationAction:
|
||||
ESP_LOGI(BLE_ANCS_TAG, "recevice Perform Notification Action");
|
||||
break;
|
||||
default:
|
||||
ESP_LOGI(BLE_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;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
//EventID values
|
||||
typedef enum {
|
||||
EventIDNotificationAdded = 0,
|
||||
EventIDNotificationModified = 1,
|
||||
EventIDNotificationRemoved = 2,
|
||||
//Reserved EventID values = 3–255
|
||||
} esp_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
|
||||
}esp_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 = 12–255
|
||||
} esp_CategoryID;
|
||||
|
||||
//CommandID values
|
||||
typedef enum {
|
||||
CommandIDGetNotificationAttributes = 0,
|
||||
CommandIDGetAppAttributes = 1,
|
||||
CommandIDPerformNotificationAction = 2,
|
||||
//Reserved CommandID values = 3–255
|
||||
} esp_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 = 8–255
|
||||
} esp_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 = 2–255
|
||||
} esp_ActionID;
|
||||
|
||||
//AppAttributeID Values
|
||||
typedef enum {
|
||||
AppAttributeIDDisplayName = 0,
|
||||
//Reserved AppAttributeID values = 1–255
|
||||
} esp_AppAttributeID;
|
||||
|
||||
typedef struct {
|
||||
uint8_t noti_attribute_id;
|
||||
uint16_t attribute_len;
|
||||
} esp_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
|
||||
} esp_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,
|
||||
} esp_attr_index;
|
||||
|
||||
#define ESP_NOTIFICATIONUID_LEN 4
|
||||
|
||||
|
||||
char *EventID_to_String(uint8_t EventID);
|
||||
char *CategoryID_to_String(uint8_t CategoryID);
|
||||
void esp_receive_apple_notification_source(uint8_t *message, uint16_t message_len);
|
||||
void esp_receive_apple_data_source(uint8_t *message, uint16_t message_len);
|
||||
char *Errcode_to_String(uint16_t status);
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,688 @@
|
||||
/*
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/event_groups.h"
|
||||
#include "esp_system.h"
|
||||
#include "esp_log.h"
|
||||
#include "nvs_flash.h"
|
||||
#include "esp_bt.h"
|
||||
|
||||
#include "esp_gap_ble_api.h"
|
||||
#include "esp_gatts_api.h"
|
||||
#include "esp_bt_defs.h"
|
||||
#include "esp_bt_main.h"
|
||||
#include "esp_gattc_api.h"
|
||||
#include "esp_gatt_defs.h"
|
||||
#include "esp_gatt_common_api.h"
|
||||
#include "ble_ancs.h"
|
||||
|
||||
#define BLE_ANCS_TAG "BLE_ANCS"
|
||||
#define EXAMPLE_DEVICE_NAME "ESP_BLE_ANCS"
|
||||
#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
|
||||
static uint8_t adv_config_done = 0;
|
||||
static bool get_service = false;
|
||||
static esp_gattc_char_elem_t *char_elem_result = NULL;
|
||||
static esp_gattc_descr_elem_t *descr_elem_result = NULL;
|
||||
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};
|
||||
|
||||
//In its basic form, the ANCS exposes three characteristics:
|
||||
// service UUID: 7905F431-B5CE-4E99-A40F-4B1E122D00D0
|
||||
uint8_t Apple_NC_UUID[16] = {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)
|
||||
uint8_t notification_source[16] = {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)
|
||||
uint8_t control_point[16] = {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)
|
||||
uint8_t data_source[16] = {0xfb, 0x7b, 0x7c, 0xce, 0x6a, 0xb3, 0x44, 0xbe, 0xb5, 0x4b, 0xd6, 0x24, 0xe9, 0xc6, 0xea, 0x22};
|
||||
|
||||
/*
|
||||
Note: There may be more characteristics present in the ANCS than the three listed above. That said, an NC may ignore any characteristic it does not recognize.
|
||||
*/
|
||||
|
||||
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 esp_bt_uuid_t apple_nc_uuid = {
|
||||
.len = ESP_UUID_LEN_128,
|
||||
};
|
||||
|
||||
static uint8_t hidd_service_uuid128[] = {
|
||||
/* LSB <--------------------------------------------------------------------------------> MSB */
|
||||
//first uuid, 16bit, [12],[13] is the value
|
||||
0xfb, 0x34, 0x9b, 0x5f, 0x80, 0x00, 0x00, 0x80, 0x00, 0x10, 0x00, 0x00, 0x12, 0x18, 0x00, 0x00,
|
||||
};
|
||||
|
||||
// config adv data
|
||||
static esp_ble_adv_data_t adv_config = {
|
||||
.set_scan_rsp = false,
|
||||
.include_txpower = false,
|
||||
.min_interval = 0x0006, //slave connection min interval, Time = min_interval * 1.25 msec
|
||||
.max_interval = 0x0010, //slave connection max interval, Time = max_interval * 1.25 msec
|
||||
.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),
|
||||
};
|
||||
// config scan response data
|
||||
static esp_ble_adv_data_t scan_rsp_config = {
|
||||
.set_scan_rsp = true,
|
||||
.include_name = true,
|
||||
.manufacturer_len = 0,
|
||||
.p_manufacturer_data = NULL,
|
||||
};
|
||||
|
||||
static esp_ble_adv_params_t adv_params = {
|
||||
.adv_int_min = 0x100,
|
||||
.adv_int_max = 0x100,
|
||||
.adv_type = ADV_TYPE_IND,
|
||||
.own_addr_type = BLE_ADDR_TYPE_RANDOM,
|
||||
.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 notification_source_handle;
|
||||
uint16_t data_source_handle;
|
||||
uint16_t contol_point_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, /* Not get the gatt_if, so initial is ESP_GATT_IF_NONE */
|
||||
},
|
||||
};
|
||||
|
||||
esp_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,
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
/*
|
||||
| CommandID(1 Byte) | NotificationUID(4 Bytes) | AttributeIDs |
|
||||
*/
|
||||
|
||||
void esp_get_notification_attributes(uint8_t *notificationUID, uint8_t num_attr, esp_noti_attr_list_t *p_attr)
|
||||
{
|
||||
uint8_t cmd[600] = {0};
|
||||
uint32_t index = 0;
|
||||
cmd[0] = CommandIDGetNotificationAttributes;
|
||||
index ++;
|
||||
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 ++] = p_attr->attribute_len;
|
||||
cmd[index ++] = (p_attr->attribute_len << 8);
|
||||
}
|
||||
p_attr ++;
|
||||
num_attr --;
|
||||
}
|
||||
|
||||
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].contol_point_handle,
|
||||
index,
|
||||
cmd,
|
||||
ESP_GATT_WRITE_TYPE_RSP,
|
||||
ESP_GATT_AUTH_REQ_NONE);
|
||||
}
|
||||
|
||||
void esp_get_app_attributes(uint8_t *appidentifier, uint16_t appidentifier_len, uint8_t num_attr, uint8_t *p_app_attrs)
|
||||
{
|
||||
uint8_t buffer[600] = {0};
|
||||
uint32_t index = 0;
|
||||
buffer[0] = CommandIDGetAppAttributes;
|
||||
index ++;
|
||||
memcpy(&buffer[index], appidentifier, appidentifier_len);
|
||||
index += appidentifier_len;
|
||||
memcpy(&buffer[index], p_app_attrs, num_attr);
|
||||
index += num_attr;
|
||||
|
||||
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].contol_point_handle,
|
||||
index,
|
||||
buffer,
|
||||
ESP_GATT_WRITE_TYPE_RSP,
|
||||
ESP_GATT_AUTH_REQ_NONE);
|
||||
}
|
||||
|
||||
void esp_perform_notification_action(uint8_t *notificationUID, uint8_t ActionID)
|
||||
{
|
||||
uint8_t buffer[600] = {0};
|
||||
uint32_t index = 0;
|
||||
buffer[0] = CommandIDPerformNotificationAction;
|
||||
index ++;
|
||||
memcpy(&buffer[index], notificationUID, ESP_NOTIFICATIONUID_LEN);
|
||||
index += ESP_NOTIFICATIONUID_LEN;
|
||||
buffer[index] = ActionID;
|
||||
index ++;
|
||||
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].contol_point_handle,
|
||||
index,
|
||||
buffer,
|
||||
ESP_GATT_WRITE_TYPE_RSP,
|
||||
ESP_GATT_AUTH_REQ_NONE);
|
||||
}
|
||||
|
||||
static void periodic_timer_callback(void* arg)
|
||||
{
|
||||
esp_timer_stop(periodic_timer);
|
||||
if (data_buffer.len > 0) {
|
||||
esp_receive_apple_data_source(data_buffer.buffer, data_buffer.len);
|
||||
memset(data_buffer.buffer, 0, data_buffer.len);
|
||||
data_buffer.len = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param)
|
||||
{
|
||||
ESP_LOGV(BLE_ANCS_TAG, "GAP_EVT, event %d\n", event);
|
||||
|
||||
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:
|
||||
//advertising start complete event to indicate advertising start successfully or failed
|
||||
if (param->adv_start_cmpl.status != ESP_BT_STATUS_SUCCESS) {
|
||||
ESP_LOGE(BLE_ANCS_TAG, "advertising start failed, error status = %x", param->adv_start_cmpl.status);
|
||||
break;
|
||||
}
|
||||
ESP_LOGI(BLE_ANCS_TAG, "advertising start success");
|
||||
break;
|
||||
case ESP_GAP_BLE_PASSKEY_REQ_EVT: /* passkey request event */
|
||||
ESP_LOGI(BLE_ANCS_TAG, "ESP_GAP_BLE_PASSKEY_REQ_EVT");
|
||||
/* Call the following function to input the passkey which is displayed on the remote device */
|
||||
//esp_ble_passkey_reply(heart_rate_profile_tab[HEART_PROFILE_APP_IDX].remote_bda, true, 0x00);
|
||||
break;
|
||||
case ESP_GAP_BLE_OOB_REQ_EVT: {
|
||||
ESP_LOGI(BLE_ANCS_TAG, "ESP_GAP_BLE_OOB_REQ_EVT");
|
||||
uint8_t tk[16] = {1}; //If you paired with OOB, both devices need to use the same tk
|
||||
esp_ble_oob_req_reply(param->ble_security.ble_req.bd_addr, tk, sizeof(tk));
|
||||
break;
|
||||
}
|
||||
case ESP_GAP_BLE_NC_REQ_EVT:
|
||||
/* The app will receive this evt when the IO has DisplayYesNO capability and the peer device IO also has DisplayYesNo capability.
|
||||
show the passkey number to the user to confirm it with the number displayed by peer device. */
|
||||
esp_ble_confirm_reply(param->ble_security.ble_req.bd_addr, true);
|
||||
ESP_LOGI(BLE_ANCS_TAG, "ESP_GAP_BLE_NC_REQ_EVT, the passkey Notify number:%d", param->ble_security.key_notif.passkey);
|
||||
break;
|
||||
case ESP_GAP_BLE_SEC_REQ_EVT:
|
||||
/* send the positive(true) security response to the peer device to accept the security request.
|
||||
If not accept the security request, should send the security response with negative(false) accept value*/
|
||||
esp_ble_gap_security_rsp(param->ble_security.ble_req.bd_addr, true);
|
||||
break;
|
||||
case ESP_GAP_BLE_PASSKEY_NOTIF_EVT: ///the app will receive this evt when the IO has Output capability and the peer device IO has Input capability.
|
||||
///show the passkey number to the user to input it in the peer device.
|
||||
ESP_LOGI(BLE_ANCS_TAG, "The passkey Notify number:%06d", 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_ANCS_TAG, "pair status = %s",param->ble_security.auth_cmpl.success ? "success" : "fail");
|
||||
if (!param->ble_security.auth_cmpl.success) {
|
||||
ESP_LOGI(BLE_ANCS_TAG, "fail reason = 0x%x",param->ble_security.auth_cmpl.fail_reason);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ESP_GAP_BLE_SET_LOCAL_PRIVACY_COMPLETE_EVT:
|
||||
if (param->local_privacy_cmpl.status != ESP_BT_STATUS_SUCCESS) {
|
||||
ESP_LOGE(BLE_ANCS_TAG, "config local privacy failed, error status = %x", param->local_privacy_cmpl.status);
|
||||
break;
|
||||
}
|
||||
|
||||
esp_err_t ret = esp_ble_gap_config_adv_data(&adv_config);
|
||||
if (ret) {
|
||||
ESP_LOGE(BLE_ANCS_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_ANCS_TAG, "config adv data failed, error code = %x", ret);
|
||||
} else {
|
||||
adv_config_done |= SCAN_RSP_CONFIG_FLAG;
|
||||
}
|
||||
|
||||
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_ANCS_TAG, "REG_EVT");
|
||||
esp_ble_gap_set_device_name(EXAMPLE_DEVICE_NAME);
|
||||
esp_ble_gap_config_local_icon (ESP_BLE_APPEARANCE_GENERIC_WATCH);
|
||||
//generate a resolvable random address
|
||||
esp_ble_gap_config_local_privacy(true);
|
||||
break;
|
||||
case ESP_GATTC_OPEN_EVT:
|
||||
if (param->open.status != ESP_GATT_OK) {
|
||||
ESP_LOGE(BLE_ANCS_TAG, "open failed, error status = %x", param->open.status);
|
||||
break;
|
||||
}
|
||||
ESP_LOGI(BLE_ANCS_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_ANCS_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_ANCS_TAG,"config mtu failed, error status = %x", param->cfg_mtu.status);
|
||||
}
|
||||
ESP_LOGI(BLE_ANCS_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_nc_uuid.uuid.uuid128, Apple_NC_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_nc_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_ANCS_TAG, "search service failed, error status = %x", param->search_cmpl.status);
|
||||
break;
|
||||
}
|
||||
ESP_LOGI(BLE_ANCS_TAG, "ESP_GATTC_SEARCH_CMPL_EVT");
|
||||
if (get_service) {
|
||||
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) {
|
||||
ESP_LOGE(BLE_ANCS_TAG, "esp_ble_gattc_get_attr_count error, %d", __LINE__);
|
||||
}
|
||||
if (count > 0) {
|
||||
char_elem_result = (esp_gattc_char_elem_t *)malloc(sizeof(esp_gattc_char_elem_t) * count);
|
||||
memset(char_elem_result, 0xff, sizeof(esp_gattc_char_elem_t) * count);
|
||||
if (!char_elem_result) {
|
||||
ESP_LOGE(BLE_ANCS_TAG, "gattc no mem");
|
||||
} else {
|
||||
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_ANCS_TAG, "esp_ble_gattc_get_all_char error, %d", __LINE__);
|
||||
}
|
||||
if (count > 0) {
|
||||
|
||||
for (int i = 0; i < count; i ++) {
|
||||
if (char_elem_result[i].uuid.len == ESP_UUID_LEN_128) {
|
||||
if (char_elem_result[i].properties & ESP_GATT_CHAR_PROP_BIT_NOTIFY
|
||||
&& memcmp(char_elem_result[i].uuid.uuid.uuid128, notification_source, 16) == 0) {
|
||||
gl_profile_tab[PROFILE_A_APP_ID].notification_source_handle = char_elem_result[i].char_handle;
|
||||
esp_ble_gattc_register_for_notify (gattc_if,
|
||||
gl_profile_tab[PROFILE_A_APP_ID].remote_bda,
|
||||
char_elem_result[i].char_handle);
|
||||
ESP_LOGI(BLE_ANCS_TAG, "Find Apple noticification source char");
|
||||
|
||||
} else if (char_elem_result[i].properties & ESP_GATT_CHAR_PROP_BIT_NOTIFY
|
||||
&& memcmp(char_elem_result[i].uuid.uuid.uuid128, data_source, 16) == 0) {
|
||||
gl_profile_tab[PROFILE_A_APP_ID].data_source_handle = char_elem_result[i].char_handle;
|
||||
esp_ble_gattc_register_for_notify (gattc_if,
|
||||
gl_profile_tab[PROFILE_A_APP_ID].remote_bda,
|
||||
char_elem_result[i].char_handle);
|
||||
ESP_LOGI(BLE_ANCS_TAG, "Find Apple data source char");
|
||||
|
||||
} else if (char_elem_result[i].properties & ESP_GATT_CHAR_PROP_BIT_WRITE
|
||||
&& memcmp(char_elem_result[i].uuid.uuid.uuid128, control_point, 16) == 0) {
|
||||
gl_profile_tab[PROFILE_A_APP_ID].contol_point_handle = char_elem_result[i].char_handle;
|
||||
ESP_LOGI(BLE_ANCS_TAG, "Find Apple control point char");
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
free(char_elem_result);
|
||||
}
|
||||
} else {
|
||||
ESP_LOGE(BLE_ANCS_TAG, "No Apple Notification Service found");
|
||||
}
|
||||
|
||||
break;
|
||||
case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
|
||||
if (param->reg_for_notify.status != ESP_GATT_OK) {
|
||||
ESP_LOGI(BLE_ANCS_TAG, "ESP_GATTC_REG_FOR_NOTIFY_EVT status %d", param->reg_for_notify.status);
|
||||
break;
|
||||
}
|
||||
uint16_t count = 0;
|
||||
uint16_t offset = 0;
|
||||
//uint16_t notify_en = 1;
|
||||
uint8_t notify_en[2] = {0x01, 0x00};
|
||||
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_DESCRIPTOR,
|
||||
gl_profile_tab[PROFILE_A_APP_ID].service_start_handle,
|
||||
gl_profile_tab[PROFILE_A_APP_ID].service_end_handle,
|
||||
param->reg_for_notify.handle,
|
||||
&count);
|
||||
if (ret_status != ESP_GATT_OK) {
|
||||
ESP_LOGE(BLE_ANCS_TAG, "esp_ble_gattc_get_attr_count error, %d", __LINE__);
|
||||
}
|
||||
if (count > 0) {
|
||||
descr_elem_result = malloc(sizeof(esp_gattc_descr_elem_t) * count);
|
||||
if (!descr_elem_result) {
|
||||
ESP_LOGE(BLE_ANCS_TAG, "malloc error, gattc no mem");
|
||||
} else {
|
||||
ret_status = esp_ble_gattc_get_all_descr(gattc_if,
|
||||
gl_profile_tab[PROFILE_A_APP_ID].conn_id,
|
||||
param->reg_for_notify.handle,
|
||||
descr_elem_result,
|
||||
&count,
|
||||
offset);
|
||||
if (ret_status != ESP_GATT_OK) {
|
||||
ESP_LOGE(BLE_ANCS_TAG, "esp_ble_gattc_get_all_descr error, %d", __LINE__);
|
||||
}
|
||||
|
||||
for (int i = 0; i < count; ++ i) {
|
||||
if (descr_elem_result[i].uuid.len == ESP_UUID_LEN_16 && descr_elem_result[i].uuid.uuid.uuid16 == ESP_GATT_UUID_CHAR_CLIENT_CONFIG) {
|
||||
esp_ble_gattc_write_char_descr (gattc_if,
|
||||
gl_profile_tab[PROFILE_A_APP_ID].conn_id,
|
||||
descr_elem_result[i].handle,
|
||||
sizeof(notify_en),
|
||||
(uint8_t *)¬ify_en,
|
||||
ESP_GATT_WRITE_TYPE_RSP,
|
||||
ESP_GATT_AUTH_REQ_NONE);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
free(descr_elem_result);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_NOTIFY_EVT:
|
||||
//esp_log_buffer_hex(BLE_ANCS_TAG, param->notify.value, param->notify.value_len);
|
||||
if (param->notify.handle == gl_profile_tab[PROFILE_A_APP_ID].notification_source_handle) {
|
||||
esp_receive_apple_notification_source(param->notify.value, param->notify.value_len);
|
||||
uint8_t *notificationUID = ¶m->notify.value[4];
|
||||
if (param->notify.value[0] == EventIDNotificationAdded && param->notify.value[2] == CategoryIDIncomingCall) {
|
||||
ESP_LOGI(BLE_ANCS_TAG, "IncomingCall, reject");
|
||||
//Call reject
|
||||
esp_perform_notification_action(notificationUID, ActionIDNegative);
|
||||
} else if (param->notify.value[0] == EventIDNotificationAdded) {
|
||||
//get more information
|
||||
ESP_LOGI(BLE_ANCS_TAG, "Get detailed information");
|
||||
esp_get_notification_attributes(notificationUID, sizeof(p_attr)/sizeof(esp_noti_attr_list_t), p_attr);
|
||||
}
|
||||
} else if (param->notify.handle == gl_profile_tab[PROFILE_A_APP_ID].data_source_handle) {
|
||||
memcpy(&data_buffer.buffer[data_buffer.len], param->notify.value, param->notify.value_len);
|
||||
data_buffer.len += param->notify.value_len;
|
||||
if (param->notify.value_len == (gl_profile_tab[PROFILE_A_APP_ID].MTU_size - 3)) {
|
||||
// cpoy and wait next packet, start timer 500ms
|
||||
esp_timer_start_periodic(periodic_timer, 500000);
|
||||
} else {
|
||||
esp_timer_stop(periodic_timer);
|
||||
esp_receive_apple_data_source(data_buffer.buffer, data_buffer.len);
|
||||
memset(data_buffer.buffer, 0, data_buffer.len);
|
||||
data_buffer.len = 0;
|
||||
}
|
||||
} else {
|
||||
ESP_LOGI(BLE_ANCS_TAG, "unknown handle, receive notify value:");
|
||||
}
|
||||
break;
|
||||
case ESP_GATTC_WRITE_DESCR_EVT:
|
||||
if (param->write.status != ESP_GATT_OK) {
|
||||
ESP_LOGE(BLE_ANCS_TAG, "write descr failed, error status = %x", param->write.status);
|
||||
break;
|
||||
}
|
||||
//ESP_LOGI(BLE_ANCS_TAG, "write descr successfully");
|
||||
break;
|
||||
case ESP_GATTC_SRVC_CHG_EVT: {
|
||||
ESP_LOGI(BLE_ANCS_TAG, "ESP_GATTC_SRVC_CHG_EVT, bd_addr:");
|
||||
esp_log_buffer_hex(BLE_ANCS_TAG, param->srvc_chg.remote_bda, 6);
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_WRITE_CHAR_EVT:
|
||||
if (param->write.status != ESP_GATT_OK) {
|
||||
char *Errstr = Errcode_to_String(param->write.status);
|
||||
if (Errstr) {
|
||||
ESP_LOGE(BLE_ANCS_TAG, "write control point error %s", Errstr);
|
||||
}
|
||||
break;
|
||||
}
|
||||
//ESP_LOGI(BLE_ANCS_TAG, "Write char success ");
|
||||
break;
|
||||
case ESP_GATTC_DISCONNECT_EVT:
|
||||
ESP_LOGI(BLE_ANCS_TAG, "ESP_GATTC_DISCONNECT_EVT, reason = 0x%x", param->disconnect.reason);
|
||||
get_service = false;
|
||||
esp_ble_gap_start_advertising(&adv_params);
|
||||
break;
|
||||
case ESP_GATTC_CONNECT_EVT:
|
||||
//ESP_LOGI(BLE_ANCS_TAG, "ESP_GATTC_CONNECT_EVT");
|
||||
//esp_log_buffer_hex("bda", param->connect.remote_bda, 6);
|
||||
memcpy(gl_profile_tab[PROFILE_A_APP_ID].remote_bda, param->connect.remote_bda, 6);
|
||||
// create gattc virtual connection
|
||||
esp_ble_gattc_open(gl_profile_tab[PROFILE_A_APP_ID].gattc_if, gl_profile_tab[PROFILE_A_APP_ID].remote_bda, BLE_ADDR_TYPE_RANDOM, true);
|
||||
break;
|
||||
case ESP_GATTC_DIS_SRVC_CMPL_EVT:
|
||||
ESP_LOGI(BLE_ANCS_TAG, "ESP_GATTC_DIS_SRVC_CMPL_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 is register event, store the gattc_if for each profile */
|
||||
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_ANCS_TAG, "Reg app failed, app_id %04x, status %d",
|
||||
param->reg.app_id,
|
||||
param->reg.status);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* If the gattc_if equal to profile A, call profile A cb handler,
|
||||
* so here call each profile's callback */
|
||||
do {
|
||||
int idx;
|
||||
for (idx = 0; idx < PROFILE_NUM; idx++) {
|
||||
if (gattc_if == ESP_GATT_IF_NONE || /* ESP_GATT_IF_NONE, not specify a certain gatt_if, need to call every profile cb function */
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
} while (0);
|
||||
}
|
||||
|
||||
void init_timer(void)
|
||||
{
|
||||
ESP_ERROR_CHECK(esp_timer_create(&periodic_timer_args, &periodic_timer));
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
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 );
|
||||
|
||||
// init timer
|
||||
init_timer();
|
||||
|
||||
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();
|
||||
ret = esp_bt_controller_init(&bt_cfg);
|
||||
if (ret) {
|
||||
ESP_LOGE(BLE_ANCS_TAG, "%s init controller failed: %s", __func__, esp_err_to_name(ret));
|
||||
return;
|
||||
}
|
||||
ret = esp_bt_controller_enable(ESP_BT_MODE_BLE);
|
||||
if (ret) {
|
||||
ESP_LOGE(BLE_ANCS_TAG, "%s enable controller failed: %s", __func__, esp_err_to_name(ret));
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGI(BLE_ANCS_TAG, "%s init bluetooth", __func__);
|
||||
ret = esp_bluedroid_init();
|
||||
if (ret) {
|
||||
ESP_LOGE(BLE_ANCS_TAG, "%s init bluetooth failed: %s", __func__, esp_err_to_name(ret));
|
||||
return;
|
||||
}
|
||||
ret = esp_bluedroid_enable();
|
||||
if (ret) {
|
||||
ESP_LOGE(BLE_ANCS_TAG, "%s enable bluetooth failed: %s", __func__, esp_err_to_name(ret));
|
||||
return;
|
||||
}
|
||||
|
||||
//register the callback function to the gattc module
|
||||
ret = esp_ble_gattc_register_callback(esp_gattc_cb);
|
||||
if (ret) {
|
||||
ESP_LOGE(BLE_ANCS_TAG, "%s gattc register error, error code = %x\n", __func__, ret);
|
||||
return;
|
||||
}
|
||||
|
||||
ret = esp_ble_gap_register_callback(gap_event_handler);
|
||||
if (ret) {
|
||||
ESP_LOGE(BLE_ANCS_TAG, "gap register error, error code = %x", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
ret = esp_ble_gattc_app_register(PROFILE_A_APP_ID);
|
||||
if (ret) {
|
||||
ESP_LOGE(BLE_ANCS_TAG, "%s gattc app register error, error code = %x\n", __func__, ret);
|
||||
}
|
||||
|
||||
ret = esp_ble_gatt_set_local_mtu(500);
|
||||
if (ret) {
|
||||
ESP_LOGE(BLE_ANCS_TAG, "set local MTU failed, error code = %x", ret);
|
||||
}
|
||||
|
||||
/* set the security iocap & auth_req & key size & init key response key parameters to the stack*/
|
||||
esp_ble_auth_req_t auth_req = ESP_LE_AUTH_REQ_SC_MITM_BOND; //bonding with peer device after authentication
|
||||
esp_ble_io_cap_t iocap = ESP_IO_CAP_NONE; //set the IO capability to No output No input
|
||||
uint8_t key_size = 16; //the key size should be 7~16 bytes
|
||||
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;
|
||||
//set static passkey
|
||||
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));
|
||||
/* If your BLE device acts as a Slave, the init_key means you hope which types of key of the master should distribute to you,
|
||||
and the response key means which key you can distribute to the master;
|
||||
If your BLE device acts as a master, the response key means you hope which types of key of the slave should distribute to you,
|
||||
and the init key means which key you can distribute to the slave. */
|
||||
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,9 @@
|
||||
#
|
||||
# Main Makefile. This is basically the same as a component makefile.
|
||||
#
|
||||
# This Makefile should, at the very least, just include $(SDK_PATH)/make/component_common.mk. By default,
|
||||
# this will take the sources in the src/ directory, compile them and link them into
|
||||
# lib(subdirectory_name).a in the build directory. This behaviour is entirely configurable,
|
||||
# please read the ESP-IDF documents if you need to do this.
|
||||
#
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
# Override some defaults so BT stack is enabled
|
||||
# by default in this example
|
||||
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
|
||||
@@ -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_compatibility_test)
|
||||
@@ -0,0 +1,11 @@
|
||||
#
|
||||
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
|
||||
# project subdirectory.
|
||||
#
|
||||
|
||||
PROJECT_NAME := ble_compatibility_test
|
||||
|
||||
COMPONENT_ADD_INCLUDEDIRS := components/include
|
||||
|
||||
include $(IDF_PATH)/make/project.mk
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
| Supported Targets | ESP32 |
|
||||
| ----------------- | ----- |
|
||||
|
||||
ESP-IDF BLE Compatibility Test Example
|
||||
=======================================
|
||||
|
||||
This demo is to test the compatibility of Bluetooth and mobile phones.
|
||||
|
||||
* ESP32 Module: ESP-WROOM-32
|
||||
|
||||
* IDF version: 7c29a39d6f9f2dfbefc49d34d34e9267afc7200d
|
||||
|
||||
* [Test case](https://github.com/espressif/esp-idf/blob/master/examples/bluetooth/bluedroid/ble/ble_compatibility_test/ble_compatibility_test_case.md)
|
||||
|
||||
* Test APK: LightBlue V1.1.3
|
||||
|
||||
* [Test report](https://github.com/espressif/esp-idf/blob/master/examples/bluetooth/bluedroid/ble/ble_compatibility_test/esp_ble_compatibility_test_report.md)
|
||||
|
||||
|
||||
@@ -0,0 +1,182 @@
|
||||
# Test Case for BLE Smartphone Compatibility
|
||||
|
||||
This document provides a test case for BLE smartphone compatibility and includes detailed procedures for various test items.
|
||||
|
||||
## Preparation
|
||||
|
||||
### What You Need
|
||||
|
||||
* ESP device which needs to flash [this test program] (https://github.com/espressif/esp-idf/blob/master/examples/bluetooth/bluedroid/ble/ble_compatibility_test/main/ble_compatibility_test.c)
|
||||
* Smartphone with LightBlue® Explorer app
|
||||
|
||||
### Initialization
|
||||
|
||||
Prior to conducting tests, please initialize the smartphone and the ESP device as follows:
|
||||
|
||||
* Set the device name as `BLE_COMP_TEST`.
|
||||
* Set the maximum transmission unit (MTU) of the device to 33 bytes, to test the assembly and division of data packets.
|
||||
* If the smartphone has been paired with the ESP device before, please delete the pairing in the Bluetooth setting as follows: `Bluetooth` -> `My Devices` -> `Find this device with "i" in a circle on the right` -> `Forget this device`. Then restart the Bluetooth service.
|
||||
* Before flashing the test program onto the ESP device, make sure to erase the contents of the flash by executing the command `make erase_flash flash` in the Terminal.
|
||||
* When the ESP device restarts, the pairing information will be erased automatically. After that, make sure that the pairing information in the Bluetooth setting of the smartphone is deleted.
|
||||
|
||||
**Note:**
|
||||
|
||||
* For tests marked with (*) further in the document, please bear in mind the following:
|
||||
* Your phone performance may affect the results of these tests. If such a test fails, it does not mean the phone fails to meet the test requirements, but that you need to arrange targeted tests.
|
||||
* Taking "Test for Connection Success Rate" as an example: if the test cannot be passed for 10 consecutive times, you need to record how many times the test was passed and then arrange targeted tests.
|
||||
* For extended testing, please use the [examples] (https://github.com/espressif/esp-idf/tree/master/examples/bluetooth) provided by Espressif.
|
||||
|
||||
|
||||
## Test for ADV Performance (*)
|
||||
|
||||
### Search Device
|
||||
|
||||
Refresh the scanning in LightBlue® Explorer to check if the device to be tested can be found quickly. Please repeat this action 10 times.
|
||||
|
||||
### Test Results
|
||||
|
||||
The test is passed, if you get the following results:
|
||||
|
||||
* The device starts advertizing and outputs the log `(0) ***** advertising start successfully *****`.
|
||||
* LightBlue® Explorer scans and successfully discovers ` BLE_COMP_TEST` each time.
|
||||
|
||||
**Note:**
|
||||
|
||||
* The device broadcasts on 3 channels, with an ADV interval of 40 ms.
|
||||
* Check if the ADV packet can be received.
|
||||
* Check if the Scan Response packet can be received.
|
||||
* The device name is included in Scan Response packets only and cannot be found in ADV packets.
|
||||
|
||||
## Test for Pairing Performance
|
||||
|
||||
### Connect Device
|
||||
|
||||
* Open the LightBlue® Explorer scan list and tap on the device name ` BLE_COMP_TEST` to establish connection.
|
||||
* ESP device prints a passkey: `The passkey notify number: 123456`.
|
||||
* A prompt on the smartphone appears asking if you want to pair. Tap on *Pair*, and then enter the passkey "123456".
|
||||
|
||||
### Test Results
|
||||
|
||||
The test is passed, if you get the following results:
|
||||
|
||||
* If the connection is successful:
|
||||
* Smartphone shows DATA beginning with `ADVERTISEMENT DATA`
|
||||
* ESP device outputs the log: `ESP_GATTS_CONNECT_EVT`
|
||||
* When the pairing is established, the device shows the following log in green: `(1) ***** pair status = success *****`
|
||||
|
||||
## Test for Service Discovery Performance
|
||||
|
||||
### Test Procedures
|
||||
|
||||
In LightBlue® Explorer, check the contents of `GATT SERVICES & CHARACTERISTICS`.
|
||||
|
||||
### Test Results
|
||||
|
||||
The test is passed, if you get the following results:
|
||||
|
||||
* Service that starts with ``000000ff`` appears at the bottom of your smartphone.
|
||||
* This service contains 3 characteristics
|
||||
* `Char_1_Short_WR`
|
||||
* `Char_2_Long_WR`
|
||||
* `Char_3_Short_Notify`
|
||||
|
||||
## Test for Read and Encrypt
|
||||
|
||||
### Test Procedures
|
||||
|
||||
Read the value of `Char_1` in LightBlue, and tap on `READ AGAIN`.
|
||||
|
||||
### Test Results
|
||||
|
||||
* Encryption is successful, if your smartphone shows the value "11 22 33 44", and the ESP device prints the log: `(2) ***** read char_1 *****`.
|
||||
* Encryption fails, if your smartphone shows a blank screen, and the ESP device outputs the error log in red: `GATT_INSUF_AUTHENTICATION: MITM Required`.
|
||||
|
||||
## Test for Short Read and Write
|
||||
|
||||
### Test Procedures
|
||||
|
||||
* Navigate to the WRITE interface in LightBlue® Explorer, and write the value "88 99" to `Char_1`.
|
||||
* Read `Char_1` and check if its value is consistent with the data you have written to it.
|
||||
|
||||
### Test Results
|
||||
|
||||
The test is passed, if you get the following results:
|
||||
|
||||
* ESP device prints the log: `(3)***** short write success *****`.
|
||||
* LightBlue® Explorer shows "88 99" below `READ AGAIN`.
|
||||
|
||||
## Test for Long Read and Write
|
||||
|
||||
### Test Procedures
|
||||
|
||||
* Navigate to the WRITE interface in LightBlue® Explorer, and write the string `0x001122…FF001122…FF` of 256 bytes to `Char_2`. The data takes up 16 lines and looks as follows:
|
||||
|
||||
```
|
||||
00112233445566778899AABBCCDDEEFF
|
||||
00112233445566778899AABBCCDDEEFF
|
||||
…
|
||||
00112233445566778899AABBCCDDEEFF
|
||||
00112233445566778899AABBCCDDEEFF
|
||||
```
|
||||
|
||||
* Read `Char_2` and check if its value is consistent with the data you have written to it.
|
||||
|
||||
### Test Results
|
||||
|
||||
The test is passed, if you get the following results:
|
||||
|
||||
* The device prints the log: ``ESP_GATTS_EXEC_WRITE_EVT, Length=256`` and ``(4) ***** long write success *****``.
|
||||
* LightBlue® Explorer shows `(5) ***** read char_2 *****` below `READ AGAIN`.
|
||||
|
||||
**Note:**
|
||||
|
||||
The data to be written can be copied from a text file and pasted into LightBlue® Explorer.
|
||||
|
||||
## Test for Short Notify
|
||||
|
||||
### Test Procedures
|
||||
|
||||
* Enter `Char_3` and tap on `SUBSCRIBE` to enable its Notify function.
|
||||
* Your phone automatically receives Notify data from the device.
|
||||
|
||||
### Test Results
|
||||
|
||||
The test is passed, if you get the following results:
|
||||
|
||||
* ESP device prints the log: `(6) ***** send notify AA BB *****`.
|
||||
* "AA BB" appears on your smartphone.
|
||||
|
||||
## Test for Connection Success Rate (*)
|
||||
|
||||
### Test procedures
|
||||
|
||||
* Break the connection
|
||||
* Re-establish the connection
|
||||
* Repeat 10 times
|
||||
|
||||
### Test Results
|
||||
|
||||
The test is passed, if you get the following results:
|
||||
|
||||
* Your phone establishes the connection successfully, and the ESP device outputs the log: `(1) ***** pair status = success *****`.
|
||||
* Your phone breaks the connection, and the device outputs the log: `ESP_GATTS_DISCONNECT_EVT`.
|
||||
* Connection can be set up each time with no issues.
|
||||
|
||||
## Test for Long Connection Stability
|
||||
|
||||
The connection must be stable throughout the tests.
|
||||
|
||||
**Note:**
|
||||
|
||||
If the existing connection breaks:
|
||||
|
||||
* LightBlue® Explorer prints `Disconnected`.
|
||||
* ESP device outputs the log: ``ESP_GATTS_DISCONNECT_EVT, reason = (0) ***** advertising start successfully *****``.
|
||||
|
||||
## Further Information
|
||||
|
||||
* If you see any log entry in red, please record it for future reference or feedback it to our engineer.
|
||||
* Tests to be added in the future:
|
||||
* Multi-connection Test
|
||||
* Automatic Re-connection Test
|
||||
|
||||
@@ -0,0 +1,905 @@
|
||||
<table class="table table-bordered table-striped table-condensed">
|
||||
<tr>
|
||||
<th colspan="14">Test Report for ESP BLE Smartphone Compatibility</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>ESP32 Module:</td>
|
||||
<td colspan="13">ESP-WROOM-32</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Commit ID:</td>
|
||||
<td colspan="13">7c29a39d6f9f2dfbefc49d34d34e9267afc7200d</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Test Demo:</td>
|
||||
<td colspan="13">https://github.com/espressif/esp-idf/tree/master/examples/bluetooth/bluedroid/ble/ble_compatibility_test</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="2"><font size="1">Phone Brand</td>
|
||||
<td rowspan="2"><font size="2">Model</td>
|
||||
<td rowspan="2"><font size="2">OS Version</td>
|
||||
<td rowspan="2"><font size="2">Test APP & Version</td>
|
||||
<td colspan="9" font size="2" align="center">Test Item</td>
|
||||
<td rowspan="2"><font size="2">Note</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><font size="2">ADV</td>
|
||||
<td><font size="2">Pairing</td>
|
||||
<td><font size="2">Service Discovery</td>
|
||||
<td><font size="2">Read & Encrypt</td>
|
||||
<td><font size="2">Short Read & Write</td>
|
||||
<td><font size="2">Long Read & Write</td>
|
||||
<td><font size="2">Short Notify</td>
|
||||
<td><font size="2">Connection Success Rate (10 times)</td>
|
||||
<td><font size="2">Long Connection Stability</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="6"><font size="2">Samsung (三星)</td>
|
||||
<td><font size="2">Galaxy S9</td>
|
||||
<td><font size="2">Android 8.0.0</td>
|
||||
<td><font size="2"><font size="2">LightBlue V1.1.3*</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">100%</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">"LightBlue" here is the abbreviation of "<font size="2">LightBlue® Explorer"</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><font size="2">Galaxy Note 4 </td>
|
||||
<td><font size="2">Android 6.0.1</td>
|
||||
<td><font size="2">LightBlue V1.1.3</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">100%</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><font size="2">Galaxy S8+</td>
|
||||
<td><font size="2">Android 8.0.0</td>
|
||||
<td><font size="2">LightBlue V1.1.3</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">100%</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><font size="2">S3 GT-I9300</td>
|
||||
<td><font size="2">Android 4.3</td>
|
||||
<td><font size="2">nRF Connect V4.10*</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">100%</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Intallation of LightBlue failed, so nRF Connect was used alternatively.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><font size="2">S4 GT-I9502</td>
|
||||
<td><font size="2">Android 8.0.0</td>
|
||||
<td><font size="2">nRF Connect V4.10*</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">100%</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Intallation of LightBlue failed, so nRF Connect was used alternatively.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><font size="2">S4 GT-I9500</td>
|
||||
<td><font size="2">Android 4.3</td>
|
||||
<td><font size="2">nRF Connect V4.10*</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">100%</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Intallation of LightBlue failed, so nRF Connect was used alternatively.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="7"><font size="2">Apple(苹果)</td>
|
||||
<td><font size="2">iPhone 5S/A1518</td>
|
||||
<td><font size="2">iOS 12.1</td>
|
||||
<td><font size="2">LightBlue V2.7</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">100%</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><font size="2">iPhone X</td>
|
||||
<td><font size="2">iOS 12.1</td>
|
||||
<td><font size="2">LightBlue V2.7</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">100%</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><font size="2">iPhone SE</td>
|
||||
<td><font size="2">iOS 10.2.1</td>
|
||||
<td><font size="2">LightBlue V2.7</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">100%</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><font size="2">iPhone 6s Plus</td>
|
||||
<td><font size="2">iOS 12.1</td>
|
||||
<td><font size="2">LightBlue V2.7</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">100%</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><font size="2">iPhone 7</td>
|
||||
<td><font size="2">iOS 12.0.1</td>
|
||||
<td><font size="2">LightBlue V2.7</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">100%</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><font size="2">iPhone 6</td>
|
||||
<td><font size="2">iOS 10.3.1</td>
|
||||
<td><font size="2">LightBlue V2.7</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">100%</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><font size="2">iPod Touch</td>
|
||||
<td><font size="2">iOS 12.0</td>
|
||||
<td><font size="2">LightBlue V2.7</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">100%</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="6"><font size="2">HUAWEI(华为)</td>
|
||||
<td><font size="2">Huawei nova 3e</td>
|
||||
<td><font size="2">Android 8.0.0</td>
|
||||
<td><font size="2">LightBlue V1.1.3</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">100%</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><font size="2">Huawei Honor Enjoy 7X</td>
|
||||
<td><font size="2">Android 7.0</td>
|
||||
<td><font size="2">LightBlue V1.1.3</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">100%</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><font size="2">Huawei Mate 10</td>
|
||||
<td><font size="2">Android 8.0.0</td>
|
||||
<td><font size="2">LightBlue V1.1.3</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">100%</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><font size="2">Huawei G9/P9 Lite</td>
|
||||
<td><font size="2">Android 6.0</td>
|
||||
<td><font size="2">LightBlue V1.1.3</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">100%</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><font size="2">Huawei nova</td>
|
||||
<td><font size="2">Android 7.0</td>
|
||||
<td><font size="2">LightBlue V1.1.3</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">100%</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><font size="2">Huawei Honor 4X</td>
|
||||
<td><font size="2">Android 5.0.2</td>
|
||||
<td><font size="2">LightBlue V1.1.3</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">100%</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Sometimes the app cannot break bluetooth connection, so you need to manually switch on and off the bluetooth.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="2"><font size="2">OPPO(欧珀)</td>
|
||||
<td><font size="2">OPPO A83</td>
|
||||
<td><font size="2">Android 7.1.1</td>
|
||||
<td><font size="2">LightBlue V1.1.3</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">100%</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><font size="2">OPPO R9s</td>
|
||||
<td><font size="2">Android 6.0.1</td>
|
||||
<td><font size="2">LightBlue V1.1.3</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">100%</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="5"><font size="2">Xiaomi(小米)</td>
|
||||
<td><font size="2">Xiaomi Mi Max 2</td>
|
||||
<td><font size="2">Android 7.1.1</td>
|
||||
<td><font size="2"><font size="2">LightBlue V1.1.3</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">100%</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><font size="2">Xiaomi 5X</td>
|
||||
<td><font size="2">Android 7.1.2</td>
|
||||
<td><font size="2"><font size="2">LightBlue V1.1.3</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">100%</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><font size="2">Xiaomi Mi Note 2</td>
|
||||
<td><font size="2">Android 7.0</td>
|
||||
<td><font size="2"><font size="2">LightBlue V1.1.3</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">100%</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><font size="2">Xiaomi Redmi Note 4</td>
|
||||
<td><font size="2">Android 6.0</td>
|
||||
<td><font size="2"><font size="2">LightBlue V1.1.3</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">100%</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><font size="2">Xiaomi Mi 5</td>
|
||||
<td><font size="2">Android 7.0</td>
|
||||
<td><font size="2"><font size="2">LightBlue V1.1.3</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">100%</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="2"><font size="2">vivo(步步高)</td>
|
||||
<td><font size="2">vivo Y85</td>
|
||||
<td><font size="2">Android 8.1.0</td>
|
||||
<td><font size="2">LightBlue V1.1.3</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">100%</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><font size="2">vivo X7</td>
|
||||
<td><font size="2">Android 5.1.1</td>
|
||||
<td><font size="2">LightBlue V1.1.3</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">100%</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="2"><font size="2">Lenovo & Motoria (联想)</td>
|
||||
<td><font size="2">Lenovo S5</td>
|
||||
<td><font size="2">Android 8.0.0</td>
|
||||
<td><font size="2">LightBlue V1.1.3</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">100%</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><font size="2">Lenovo K5</td>
|
||||
<td><font size="2">Android 8.0.0</td>
|
||||
<td><font size="2">LightBlue V1.1.3</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">100%</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="1"><font size="2">ZTE & Nubia(中兴)</td>
|
||||
<td><font size="2">Nubia Z17 Mini</td>
|
||||
<td><font size="2">Android 6.0.1</td>
|
||||
<td><font size="2">LightBlue V1.1.3</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">100%</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="2"><font size="2">Gionee(金立)</td>
|
||||
<td><font size="2">Gionee S11</td>
|
||||
<td><font size="2">Android 7.11</td>
|
||||
<td><font size="2">LightBlue V1.1.3</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">100%</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><font size="2">Gionee GN9004</td>
|
||||
<td><font size="2">Android 4.3</td>
|
||||
<td><font size="2">nRF Connect V4.10*</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">100%</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Intallation of LightBlue failed, so nRF Connect was used alternatively.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><font size="2">Google(谷歌)</td>
|
||||
<td><font size="2">LG Nexus 4*</td>
|
||||
<td><font size="2">Android 5.1.1</td>
|
||||
<td><font size="2">LightBlue V1.1.3</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">100%</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">BLE scan performance of this phone is poor.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><font size="2">Sony(索尼)</td>
|
||||
<td><font size="2">Sony Xperia XZ</td>
|
||||
<td><font size="2">Android 8.0.0</td>
|
||||
<td><font size="2">LightBlue V1.1.3</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">100%</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><font size="2">HTC(宏达电)</td>
|
||||
<td><font size="2">HTC U11</td>
|
||||
<td><font size="2">Android 7.1.1</td>
|
||||
<td><font size="2">LightBlue V1.1.3</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">100%</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><font size="2">Essential</td>
|
||||
<td><font size="2">Essential Phone</td>
|
||||
<td><font size="2">Android 7.1.1</td>
|
||||
<td><font size="2">LightBlue V1.1.3</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">100%</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="2"><font size="2">Meizu(魅族)</td>
|
||||
<td><font size="2">Meilan Note 3</td>
|
||||
<td><font size="2">Android 5.1</td>
|
||||
<td><font size="2">LightBlue V1.1.3</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">100%</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><font size="2">Meilan E</td>
|
||||
<td><font size="2">Android 5.2.1</td>
|
||||
<td><font size="2">LightBlue V1.1.3</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">100%</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><font size="2">Smartisan(锤子)</td>
|
||||
<td><font size="2">Smartisan Nut Pro 2</td>
|
||||
<td><font size="2">Android 7.1.1</td>
|
||||
<td><font size="2">LightBlue V1.1.3</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">100%</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><font size="2">Sharp(夏普)</td>
|
||||
<td><font size="2">Sharp AQUOS S3 mini </td>
|
||||
<td><font size="2">Android 7.1.1</td>
|
||||
<td><font size="2">LightBlue V1.1.3</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">100%</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><font size="2">Hisense(海信)</td>
|
||||
<td><font size="2">HiSense Small Dolphin 2(海信小海豚 2)</td>
|
||||
<td><font size="2">Android 7.1.2</td>
|
||||
<td><font size="2">LightBlue V1.1.3</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">100%</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="2"><font size="2">360(奇虎)</td>
|
||||
<td><font size="2">360 N6 Lite</td>
|
||||
<td><font size="2">Android 7.1.1</td>
|
||||
<td><font size="2">LightBlue V1.1.3</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">100%</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><font size="2">360 N5</td>
|
||||
<td><font size="2">Android 6.0.1</td>
|
||||
<td><font size="2">LightBlue V1.1.3</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">100%</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="2"><font size="2">Xiaolajiao(小辣椒)</td>
|
||||
<td><font size="2">Red Chilli 4A(红辣椒 4A)</td>
|
||||
<td><font size="2">Android 3.2.0</td>
|
||||
<td><font size="2">LightBlue V1.1.3</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">100%</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><font size="2">Red Chilli Enjoy 6A(红辣椒畅玩 6A)</td>
|
||||
<td><font size="2">Android 5.1.1</td>
|
||||
<td><font size="2">LightBlue V1.1.3</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">100%</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="2"><font size="2"><font size="2">Letv(乐视)</td>
|
||||
<td><font size="2">LeTV LeEcoo Le S3</td>
|
||||
<td><font size="2">Android 6.0</td>
|
||||
<td><font size="2">LightBlue V1.1.3</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">100%</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><font size="2">LeTV LeEoo Le1 (X600)</td>
|
||||
<td><font size="2">Android 5.0.2</td>
|
||||
<td><font size="2">LightBlue V1.1.3</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">100%</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><font size="2">Coolpad(酷派)</td>
|
||||
<td><font size="2">Coolpad Cool 1 dual</td>
|
||||
<td><font size="2">Android 6.0.1</td>
|
||||
<td><font size="2">LightBlue V1.1.3</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">100%</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><font size="2">Doov(朵唯)</td>
|
||||
<td><font size="2">Doov A15S</td>
|
||||
<td><font size="2">Android 5.1.1</td>
|
||||
<td><font size="2">LightBlue V1.1.3</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">100%</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><font size="2">AGM(艾捷莫)</td>
|
||||
<td><font size="2">AGM X1</td>
|
||||
<td><font size="2">Android 5.1</td>
|
||||
<td><font size="2">LightBlue V1.1.3</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">100%</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><font size="2">CMCC(中国移动)</td>
|
||||
<td><font size="2">CMCC N3</td>
|
||||
<td><font size="2">Android 7.1.2</td>
|
||||
<td><font size="2">LightBlue V1.1.3</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">100%</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><font size="2">Meitu(美图)</td>
|
||||
<td><font size="2">Meitu M8s</td>
|
||||
<td><font size="2">Android 7.1.1</td>
|
||||
<td><font size="2">LightBlue V1.1.3</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">100%</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><font size="2">K-Touch(天语)</td>
|
||||
<td><font size="2">K-Touch X11</td>
|
||||
<td><font size="2">Android 6.1</td>
|
||||
<td><font size="2">LightBlue V1.1.3</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">100%</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><font size="2">YEPEN(誉品)</td>
|
||||
<td><font size="2">YEPEN I7S</td>
|
||||
<td><font size="2">Android 6.0</td>
|
||||
<td><font size="2">LightBlue V1.1.3</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">100%</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><font size="2">MOTO</td>
|
||||
<td><font size="2">Z2 Paly</td>
|
||||
<td><font size="2">Android 7.1.1</td>
|
||||
<td><font size="2">LightBlue V1.1.3</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td><font size="2">100%</td>
|
||||
<td><font size="2">Pass</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
</table>
|
||||
@@ -0,0 +1,2 @@
|
||||
idf_component_register(SRCS "ble_compatibility_test.c"
|
||||
INCLUDE_DIRS ".")
|
||||
@@ -0,0 +1,700 @@
|
||||
/*
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
/********************************************************************************
|
||||
*
|
||||
* This file is for gatt server. It can send adv data, and get connected by client.
|
||||
*
|
||||
*********************************************************************************/
|
||||
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/event_groups.h"
|
||||
#include "esp_system.h"
|
||||
#include "esp_log.h"
|
||||
#include "nvs_flash.h"
|
||||
#include "esp_bt.h"
|
||||
|
||||
#include "esp_gap_ble_api.h"
|
||||
#include "esp_gatts_api.h"
|
||||
#include "esp_bt_main.h"
|
||||
#include "ble_compatibility_test.h"
|
||||
#include "esp_gatt_common_api.h"
|
||||
|
||||
#define DEBUG_ON 0
|
||||
|
||||
#if DEBUG_ON
|
||||
#define EXAMPLE_DEBUG ESP_LOGI
|
||||
#else
|
||||
#define EXAMPLE_DEBUG( tag, format, ... )
|
||||
#endif
|
||||
|
||||
#define EXAMPLE_TAG "BLE_COMP"
|
||||
|
||||
#define PROFILE_NUM 1
|
||||
#define PROFILE_APP_IDX 0
|
||||
#define ESP_APP_ID 0x55
|
||||
#define SAMPLE_DEVICE_NAME "BLE_COMP_TEST"
|
||||
#define SVC_INST_ID 0
|
||||
|
||||
/* The max length of characteristic value. When the gatt client write or prepare write,
|
||||
* the data length must be less than GATTS_EXAMPLE_CHAR_VAL_LEN_MAX.
|
||||
*/
|
||||
#define GATTS_EXAMPLE_CHAR_VAL_LEN_MAX 500
|
||||
#define LONG_CHAR_VAL_LEN 500
|
||||
#define SHORT_CHAR_VAL_LEN 10
|
||||
#define GATTS_NOTIFY_FIRST_PACKET_LEN_MAX 20
|
||||
|
||||
#define PREPARE_BUF_MAX_SIZE 1024
|
||||
#define CHAR_DECLARATION_SIZE (sizeof(uint8_t))
|
||||
|
||||
#define ADV_CONFIG_FLAG (1 << 0)
|
||||
#define SCAN_RSP_CONFIG_FLAG (1 << 1)
|
||||
|
||||
static uint8_t adv_config_done = 0;
|
||||
|
||||
uint16_t gatt_db_handle_table[HRS_IDX_NB];
|
||||
|
||||
typedef struct {
|
||||
uint8_t *prepare_buf;
|
||||
int prepare_len;
|
||||
} prepare_type_env_t;
|
||||
|
||||
static prepare_type_env_t prepare_write_env;
|
||||
|
||||
//#define CONFIG_SET_RAW_ADV_DATA
|
||||
#ifdef CONFIG_SET_RAW_ADV_DATA
|
||||
static uint8_t raw_adv_data[] = {
|
||||
/* flags */
|
||||
0x02, 0x01, 0x06,
|
||||
/* tx power*/
|
||||
0x02, 0x0a, 0xeb,
|
||||
/* service uuid */
|
||||
0x03, 0x03, 0xFF, 0x00,
|
||||
/* device name */
|
||||
0x0E, 0x09, 'B', 'L', 'E', '_', 'C', 'O','M', 'P', '_', 'T','E', 'S', 'T'
|
||||
};
|
||||
static uint8_t raw_scan_rsp_data[] = {
|
||||
/* flags */
|
||||
0x02, 0x01, 0x06,
|
||||
/* tx power */
|
||||
0x02, 0x0a, 0xeb,
|
||||
/* service uuid */
|
||||
0x03, 0x03, 0xFF,0x00
|
||||
};
|
||||
|
||||
#else
|
||||
static uint8_t service_uuid[16] = {
|
||||
/* LSB <--------------------------------------------------------------------------------> MSB */
|
||||
//first uuid, 16bit, [12],[13] is the value
|
||||
0xfb, 0x34, 0x9b, 0x5f, 0x80, 0x00, 0x00, 0x80, 0x00, 0x10, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00,
|
||||
};
|
||||
|
||||
/* The length of adv data must be less than 31 bytes */
|
||||
static esp_ble_adv_data_t adv_data = {
|
||||
.set_scan_rsp = false,
|
||||
.include_name = true,
|
||||
.include_txpower = true,
|
||||
.min_interval = 0x20,
|
||||
.max_interval = 0x40,
|
||||
.appearance = 0x00,
|
||||
.manufacturer_len = 0, //TEST_MANUFACTURER_DATA_LEN,
|
||||
.p_manufacturer_data = NULL, //test_manufacturer,
|
||||
.service_data_len = 0,
|
||||
.p_service_data = NULL,
|
||||
.service_uuid_len = sizeof(service_uuid),
|
||||
.p_service_uuid = service_uuid,
|
||||
.flag = (ESP_BLE_ADV_FLAG_GEN_DISC | ESP_BLE_ADV_FLAG_BREDR_NOT_SPT),
|
||||
};
|
||||
|
||||
// scan response data
|
||||
static esp_ble_adv_data_t scan_rsp_data = {
|
||||
.set_scan_rsp = true,
|
||||
.include_name = true,
|
||||
.include_txpower = true,
|
||||
.min_interval = 0x20,
|
||||
.max_interval = 0x40,
|
||||
.appearance = 0x00,
|
||||
.manufacturer_len = 0, //TEST_MANUFACTURER_DATA_LEN,
|
||||
.p_manufacturer_data = NULL, //&test_manufacturer[0],
|
||||
.service_data_len = 0,
|
||||
.p_service_data = NULL,
|
||||
.service_uuid_len = 16,
|
||||
.p_service_uuid = service_uuid,
|
||||
.flag = (ESP_BLE_ADV_FLAG_GEN_DISC | ESP_BLE_ADV_FLAG_BREDR_NOT_SPT),
|
||||
};
|
||||
#endif /* CONFIG_SET_RAW_ADV_DATA */
|
||||
|
||||
static esp_ble_adv_params_t adv_params = {
|
||||
.adv_int_min = 0x40,
|
||||
.adv_int_max = 0x40,
|
||||
.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 gatts_profile_inst {
|
||||
esp_gatts_cb_t gatts_cb;
|
||||
uint16_t gatts_if;
|
||||
uint16_t app_id;
|
||||
uint16_t conn_id;
|
||||
uint16_t service_handle;
|
||||
esp_gatt_srvc_id_t service_id;
|
||||
uint16_t char_handle;
|
||||
esp_bt_uuid_t char_uuid;
|
||||
esp_gatt_perm_t perm;
|
||||
esp_gatt_char_prop_t property;
|
||||
uint16_t descr_handle;
|
||||
esp_bt_uuid_t descr_uuid;
|
||||
};
|
||||
|
||||
static void gatts_profile_event_handler(esp_gatts_cb_event_t event,
|
||||
esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param);
|
||||
|
||||
/* One gatt-based profile one app_id and one gatts_if, this array will store the gatts_if returned by ESP_GATTS_REG_EVT */
|
||||
static struct gatts_profile_inst heart_rate_profile_tab[PROFILE_NUM] = {
|
||||
[PROFILE_APP_IDX] = {
|
||||
.gatts_cb = gatts_profile_event_handler,
|
||||
.gatts_if = ESP_GATT_IF_NONE, /* Not get the gatt_if, so initial is ESP_GATT_IF_NONE */
|
||||
},
|
||||
};
|
||||
|
||||
/* Service */
|
||||
static const uint16_t GATTS_SERVICE_UUID_TEST = 0x00FF;
|
||||
static const uint16_t CHAR_1_SHORT_WR = 0xFF01;
|
||||
static const uint16_t CHAR_2_LONG_WR = 0xFF02;
|
||||
static const uint16_t CHAR_3_SHORT_NOTIFY = 0xFF03;
|
||||
|
||||
static const uint16_t primary_service_uuid = ESP_GATT_UUID_PRI_SERVICE;
|
||||
static const uint16_t character_declaration_uuid = ESP_GATT_UUID_CHAR_DECLARE;
|
||||
static const uint16_t character_client_config_uuid = ESP_GATT_UUID_CHAR_CLIENT_CONFIG;
|
||||
static const uint16_t character_user_description = ESP_GATT_UUID_CHAR_DESCRIPTION;
|
||||
static const uint8_t char_prop_notify = ESP_GATT_CHAR_PROP_BIT_NOTIFY;
|
||||
static const uint8_t char_prop_read_write = ESP_GATT_CHAR_PROP_BIT_WRITE | ESP_GATT_CHAR_PROP_BIT_READ;
|
||||
static const uint8_t char1_name[] = "Char_1_Short_WR";
|
||||
static const uint8_t char2_name[] = "Char_2_Long_WR";
|
||||
static const uint8_t char3_name[] = "Char_3_Short_Notify";
|
||||
static const uint8_t char_ccc[2] = {0x00, 0x00};
|
||||
static const uint8_t char_value[4] = {0x11, 0x22, 0x33, 0x44};
|
||||
|
||||
|
||||
/* Full Database Description - Used to add attributes into the database */
|
||||
static const esp_gatts_attr_db_t gatt_db[HRS_IDX_NB] =
|
||||
{
|
||||
// Service Declaration
|
||||
[IDX_SVC] =
|
||||
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&primary_service_uuid, ESP_GATT_PERM_READ,
|
||||
sizeof(uint16_t), sizeof(GATTS_SERVICE_UUID_TEST), (uint8_t *)&GATTS_SERVICE_UUID_TEST}},
|
||||
|
||||
/* Characteristic Declaration */
|
||||
[IDX_CHAR_A] =
|
||||
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_declaration_uuid, ESP_GATT_PERM_READ,
|
||||
CHAR_DECLARATION_SIZE, CHAR_DECLARATION_SIZE, (uint8_t *)&char_prop_read_write}},
|
||||
|
||||
/* Characteristic Value */
|
||||
[IDX_CHAR_VAL_A] =
|
||||
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&CHAR_1_SHORT_WR, ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE | ESP_GATT_PERM_READ_ENC_MITM,
|
||||
SHORT_CHAR_VAL_LEN, sizeof(char_value), (uint8_t *)char_value}},
|
||||
|
||||
/* Characteristic User Descriptor */
|
||||
[IDX_CHAR_CFG_A] =
|
||||
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_user_description, ESP_GATT_PERM_READ,
|
||||
sizeof(char1_name), sizeof(char1_name), (uint8_t *)char1_name}},
|
||||
|
||||
/* Characteristic Declaration */
|
||||
[IDX_CHAR_B] =
|
||||
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_declaration_uuid, ESP_GATT_PERM_READ,
|
||||
CHAR_DECLARATION_SIZE, CHAR_DECLARATION_SIZE, (uint8_t *)&char_prop_read_write}},
|
||||
|
||||
/* Characteristic Value */
|
||||
[IDX_CHAR_VAL_B] =
|
||||
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&CHAR_2_LONG_WR, ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE,
|
||||
LONG_CHAR_VAL_LEN, sizeof(char_value), (uint8_t *)char_value}},
|
||||
|
||||
/* Characteristic User Descriptor */
|
||||
[IDX_CHAR_CFG_B] =
|
||||
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_user_description, ESP_GATT_PERM_READ,
|
||||
sizeof(char2_name), sizeof(char2_name), (uint8_t *)char2_name}},
|
||||
|
||||
/* Characteristic Declaration */
|
||||
[IDX_CHAR_C] =
|
||||
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_declaration_uuid, ESP_GATT_PERM_READ,
|
||||
CHAR_DECLARATION_SIZE, CHAR_DECLARATION_SIZE, (uint8_t *)&char_prop_notify}},
|
||||
|
||||
/* Characteristic Value */
|
||||
[IDX_CHAR_VAL_C] =
|
||||
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&CHAR_3_SHORT_NOTIFY, 0,
|
||||
LONG_CHAR_VAL_LEN, sizeof(char_value), (uint8_t *)char_value}},
|
||||
|
||||
/* Characteristic User Descriptor */
|
||||
[IDX_CHAR_CFG_C] =
|
||||
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_user_description, ESP_GATT_PERM_READ,
|
||||
sizeof(char3_name), sizeof(char3_name), (uint8_t *)char3_name}},
|
||||
|
||||
/* Characteristic Client Configuration Descriptor */
|
||||
[IDX_CHAR_CFG_C_2] =
|
||||
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_client_config_uuid, ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE,
|
||||
sizeof(uint16_t), sizeof(char_ccc), (uint8_t *)char_ccc}},
|
||||
|
||||
};
|
||||
|
||||
static void show_bonded_devices(void)
|
||||
{
|
||||
int dev_num = esp_ble_get_bond_device_num();
|
||||
|
||||
esp_ble_bond_dev_t *dev_list = (esp_ble_bond_dev_t *)malloc(sizeof(esp_ble_bond_dev_t) * dev_num);
|
||||
esp_ble_get_bond_device_list(&dev_num, dev_list);
|
||||
EXAMPLE_DEBUG(EXAMPLE_TAG, "Bonded devices number : %d\n", dev_num);
|
||||
|
||||
EXAMPLE_DEBUG(EXAMPLE_TAG, "Bonded devices list : %d\n", dev_num);
|
||||
for (int i = 0; i < dev_num; i++) {
|
||||
#if DEBUG_ON
|
||||
esp_log_buffer_hex(EXAMPLE_TAG, (void *)dev_list[i].bd_addr, sizeof(esp_bd_addr_t));
|
||||
#endif
|
||||
}
|
||||
|
||||
free(dev_list);
|
||||
}
|
||||
|
||||
static void __attribute__((unused)) remove_all_bonded_devices(void)
|
||||
{
|
||||
int dev_num = esp_ble_get_bond_device_num();
|
||||
|
||||
esp_ble_bond_dev_t *dev_list = (esp_ble_bond_dev_t *)malloc(sizeof(esp_ble_bond_dev_t) * dev_num);
|
||||
esp_ble_get_bond_device_list(&dev_num, dev_list);
|
||||
for (int i = 0; i < dev_num; i++) {
|
||||
esp_ble_remove_bond_device(dev_list[i].bd_addr);
|
||||
}
|
||||
|
||||
free(dev_list);
|
||||
}
|
||||
|
||||
static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param)
|
||||
{
|
||||
switch (event) {
|
||||
#ifdef CONFIG_SET_RAW_ADV_DATA
|
||||
case ESP_GAP_BLE_ADV_DATA_RAW_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_SCAN_RSP_DATA_RAW_SET_COMPLETE_EVT:
|
||||
adv_config_done &= (~SCAN_RSP_CONFIG_FLAG);
|
||||
if (adv_config_done == 0){
|
||||
esp_ble_gap_start_advertising(&adv_params);
|
||||
}
|
||||
break;
|
||||
#else
|
||||
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_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;
|
||||
#endif
|
||||
case ESP_GAP_BLE_ADV_START_COMPLETE_EVT:
|
||||
/* advertising start complete event to indicate advertising start successfully or failed */
|
||||
if (param->adv_start_cmpl.status != ESP_BT_STATUS_SUCCESS) {
|
||||
ESP_LOGE(EXAMPLE_TAG, "advertising start failed");
|
||||
}else{
|
||||
ESP_LOGI(EXAMPLE_TAG, "(0) ***** advertising start successfully ***** \n");
|
||||
}
|
||||
break;
|
||||
case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT:
|
||||
if (param->adv_stop_cmpl.status != ESP_BT_STATUS_SUCCESS) {
|
||||
ESP_LOGE(EXAMPLE_TAG, "Advertising stop failed");
|
||||
}
|
||||
else {
|
||||
ESP_LOGI(EXAMPLE_TAG, "Stop adv successfully\n");
|
||||
}
|
||||
break;
|
||||
case ESP_GAP_BLE_UPDATE_CONN_PARAMS_EVT:
|
||||
EXAMPLE_DEBUG(EXAMPLE_TAG, "update connection params status = %d, min_int = %d, max_int = %d,conn_int = %d,latency = %d, timeout = %d",
|
||||
param->update_conn_params.status,
|
||||
param->update_conn_params.min_int,
|
||||
param->update_conn_params.max_int,
|
||||
param->update_conn_params.conn_int,
|
||||
param->update_conn_params.latency,
|
||||
param->update_conn_params.timeout);
|
||||
break;
|
||||
case ESP_GAP_BLE_PASSKEY_REQ_EVT: /* passkey request event */
|
||||
EXAMPLE_DEBUG(EXAMPLE_TAG, "ESP_GAP_BLE_PASSKEY_REQ_EVT");
|
||||
//esp_ble_passkey_reply(heart_rate_profile_tab[HEART_PROFILE_APP_IDX].remote_bda, true, 0x00);
|
||||
break;
|
||||
|
||||
case ESP_GAP_BLE_NC_REQ_EVT:
|
||||
/* The app will receive this event when the IO has DisplayYesNO capability and the peer device IO also has DisplayYesNo capability.
|
||||
show the passkey number to the user to confirm it with the number displayed by peer device. */
|
||||
ESP_LOGI(EXAMPLE_TAG, "ESP_GAP_BLE_NC_REQ_EVT, the passkey Notify number:%d", param->ble_security.key_notif.passkey);
|
||||
break;
|
||||
case ESP_GAP_BLE_SEC_REQ_EVT:
|
||||
/* send the positive(true) security response to the peer device to accept the security request.
|
||||
If not accept the security request, should send the security response with negative(false) accept value*/
|
||||
esp_ble_gap_security_rsp(param->ble_security.ble_req.bd_addr, true);
|
||||
break;
|
||||
case ESP_GAP_BLE_PASSKEY_NOTIF_EVT: ///the app will receive this evt when the IO has Output capability and the peer device IO has Input capability.
|
||||
///show the passkey number to the user to input it in the peer device.
|
||||
ESP_LOGI(EXAMPLE_TAG, "The passkey notify number:%d", param->ble_security.key_notif.passkey);
|
||||
break;
|
||||
case ESP_GAP_BLE_KEY_EVT:
|
||||
//shows the ble key info share with peer device to the user.
|
||||
EXAMPLE_DEBUG(EXAMPLE_TAG, "key type = %s", esp_key_type_to_str(param->ble_security.ble_key.key_type));
|
||||
break;
|
||||
case ESP_GAP_BLE_AUTH_CMPL_EVT: {
|
||||
esp_bd_addr_t bd_addr;
|
||||
memcpy(bd_addr, param->ble_security.auth_cmpl.bd_addr, sizeof(esp_bd_addr_t));
|
||||
EXAMPLE_DEBUG(EXAMPLE_TAG, "remote BD_ADDR: %08x%04x",\
|
||||
(bd_addr[0] << 24) + (bd_addr[1] << 16) + (bd_addr[2] << 8) + bd_addr[3],
|
||||
(bd_addr[4] << 8) + bd_addr[5]);
|
||||
EXAMPLE_DEBUG(EXAMPLE_TAG, "address type = %d", param->ble_security.auth_cmpl.addr_type);
|
||||
if (param->ble_security.auth_cmpl.success){
|
||||
ESP_LOGI(EXAMPLE_TAG, "(1) ***** pair status = success ***** \n");
|
||||
}
|
||||
else {
|
||||
ESP_LOGI(EXAMPLE_TAG, "***** pair status = fail, reason = 0x%x *****\n", param->ble_security.auth_cmpl.fail_reason);
|
||||
}
|
||||
show_bonded_devices();
|
||||
break;
|
||||
}
|
||||
case ESP_GAP_BLE_REMOVE_BOND_DEV_COMPLETE_EVT: {
|
||||
EXAMPLE_DEBUG(EXAMPLE_TAG, "ESP_GAP_BLE_REMOVE_BOND_DEV_COMPLETE_EVT status = %d", param->remove_bond_dev_cmpl.status);
|
||||
#if DEBUG_ON
|
||||
esp_log_buffer_hex(EXAMPLE_TAG, (void *)param->remove_bond_dev_cmpl.bd_addr, sizeof(esp_bd_addr_t));
|
||||
#endif
|
||||
EXAMPLE_DEBUG(EXAMPLE_TAG, "------------------------------------");
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void example_prepare_write_event_env(esp_gatt_if_t gatts_if, prepare_type_env_t *prepare_write_env, esp_ble_gatts_cb_param_t *param)
|
||||
{
|
||||
EXAMPLE_DEBUG(EXAMPLE_TAG, "prepare write, handle = %d, value len = %d", param->write.handle, param->write.len);
|
||||
esp_gatt_status_t status = ESP_GATT_OK;
|
||||
if (prepare_write_env->prepare_buf == NULL) {
|
||||
prepare_write_env->prepare_buf = (uint8_t *)malloc(PREPARE_BUF_MAX_SIZE * sizeof(uint8_t));
|
||||
prepare_write_env->prepare_len = 0;
|
||||
if (prepare_write_env->prepare_buf == NULL) {
|
||||
ESP_LOGE(EXAMPLE_TAG, "%s, Gatt_server prep no mem", __func__);
|
||||
status = ESP_GATT_NO_RESOURCES;
|
||||
}
|
||||
} else {
|
||||
if(param->write.offset > PREPARE_BUF_MAX_SIZE) {
|
||||
status = ESP_GATT_INVALID_OFFSET;
|
||||
} else if ((param->write.offset + param->write.len) > PREPARE_BUF_MAX_SIZE) {
|
||||
status = ESP_GATT_INVALID_ATTR_LEN;
|
||||
}
|
||||
}
|
||||
/*send response when param->write.need_rsp is true */
|
||||
if (param->write.need_rsp){
|
||||
esp_gatt_rsp_t *gatt_rsp = (esp_gatt_rsp_t *)malloc(sizeof(esp_gatt_rsp_t));
|
||||
if (gatt_rsp != NULL){
|
||||
gatt_rsp->attr_value.len = param->write.len;
|
||||
gatt_rsp->attr_value.handle = param->write.handle;
|
||||
gatt_rsp->attr_value.offset = param->write.offset;
|
||||
gatt_rsp->attr_value.auth_req = ESP_GATT_AUTH_REQ_NONE;
|
||||
memcpy(gatt_rsp->attr_value.value, param->write.value, param->write.len);
|
||||
esp_err_t response_err = esp_ble_gatts_send_response(gatts_if, param->write.conn_id, param->write.trans_id, status, gatt_rsp);
|
||||
if (response_err != ESP_OK){
|
||||
ESP_LOGE(EXAMPLE_TAG, "Send response error");
|
||||
}
|
||||
free(gatt_rsp);
|
||||
}else{
|
||||
ESP_LOGE(EXAMPLE_TAG, "%s, malloc failed", __func__);
|
||||
}
|
||||
}
|
||||
if (status != ESP_GATT_OK){
|
||||
return;
|
||||
}
|
||||
memcpy(prepare_write_env->prepare_buf + param->write.offset,
|
||||
param->write.value,
|
||||
param->write.len);
|
||||
prepare_write_env->prepare_len += param->write.len;
|
||||
|
||||
}
|
||||
uint8_t long_write[16] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF};
|
||||
void example_exec_write_event_env(prepare_type_env_t *prepare_write_env, esp_ble_gatts_cb_param_t *param){
|
||||
if (param->exec_write.exec_write_flag == ESP_GATT_PREP_WRITE_EXEC && prepare_write_env->prepare_buf){
|
||||
if(prepare_write_env->prepare_len == 256) {
|
||||
bool long_write_success = true;
|
||||
for(uint16_t i = 0; i < prepare_write_env->prepare_len; i ++) {
|
||||
if(prepare_write_env->prepare_buf[i] != long_write[i%16]) {
|
||||
long_write_success = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(long_write_success) {
|
||||
ESP_LOGI(EXAMPLE_TAG, "(4) ***** long write success ***** \n");
|
||||
}
|
||||
}
|
||||
}else{
|
||||
ESP_LOGI(EXAMPLE_TAG,"ESP_GATT_PREP_WRITE_CANCEL");
|
||||
}
|
||||
if (prepare_write_env->prepare_buf) {
|
||||
free(prepare_write_env->prepare_buf);
|
||||
prepare_write_env->prepare_buf = NULL;
|
||||
}
|
||||
prepare_write_env->prepare_len = 0;
|
||||
}
|
||||
|
||||
static void gatts_profile_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param)
|
||||
{
|
||||
switch (event) {
|
||||
case ESP_GATTS_REG_EVT:{
|
||||
esp_err_t set_dev_name_ret = esp_ble_gap_set_device_name(SAMPLE_DEVICE_NAME);
|
||||
if (set_dev_name_ret){
|
||||
ESP_LOGE(EXAMPLE_TAG, "set device name failed, error code = %x", set_dev_name_ret);
|
||||
}
|
||||
#ifdef CONFIG_SET_RAW_ADV_DATA
|
||||
esp_err_t raw_adv_ret = esp_ble_gap_config_adv_data_raw(raw_adv_data, sizeof(raw_adv_data));
|
||||
if (raw_adv_ret){
|
||||
ESP_LOGE(EXAMPLE_TAG, "config raw adv data failed, error code = %x ", raw_adv_ret);
|
||||
}
|
||||
adv_config_done |= ADV_CONFIG_FLAG;
|
||||
esp_err_t raw_scan_ret = esp_ble_gap_config_scan_rsp_data_raw(raw_scan_rsp_data, sizeof(raw_scan_rsp_data));
|
||||
if (raw_scan_ret){
|
||||
ESP_LOGE(EXAMPLE_TAG, "config raw scan rsp data failed, error code = %x", raw_scan_ret);
|
||||
}
|
||||
adv_config_done |= SCAN_RSP_CONFIG_FLAG;
|
||||
#else
|
||||
//config adv data
|
||||
esp_err_t ret = esp_ble_gap_config_adv_data(&adv_data);
|
||||
if (ret){
|
||||
ESP_LOGE(EXAMPLE_TAG, "config adv data failed, error code = %x", ret);
|
||||
}
|
||||
adv_config_done |= ADV_CONFIG_FLAG;
|
||||
//config scan response data
|
||||
ret = esp_ble_gap_config_adv_data(&scan_rsp_data);
|
||||
if (ret){
|
||||
ESP_LOGE(EXAMPLE_TAG, "config scan response data failed, error code = %x", ret);
|
||||
}
|
||||
adv_config_done |= SCAN_RSP_CONFIG_FLAG;
|
||||
#endif
|
||||
esp_err_t create_attr_ret = esp_ble_gatts_create_attr_tab(gatt_db, gatts_if, HRS_IDX_NB, SVC_INST_ID);
|
||||
if (create_attr_ret){
|
||||
ESP_LOGE(EXAMPLE_TAG, "create attr table failed, error code = %x", create_attr_ret);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ESP_GATTS_READ_EVT:
|
||||
//ESP_LOGE(EXAMPLE_TAG, "ESP_GATTS_READ_EVT, handle=0x%d, offset=%d", param->read.handle, param->read.offset);
|
||||
if(gatt_db_handle_table[IDX_CHAR_VAL_A] == param->read.handle) {
|
||||
ESP_LOGE(EXAMPLE_TAG, "(2) ***** read char1 ***** \n");
|
||||
}
|
||||
if(gatt_db_handle_table[IDX_CHAR_VAL_B] == param->read.handle) {
|
||||
ESP_LOGE(EXAMPLE_TAG, "(5) ***** read char2 ***** \n");
|
||||
}
|
||||
break;
|
||||
case ESP_GATTS_WRITE_EVT:
|
||||
if (!param->write.is_prep){
|
||||
// the data length of gattc write must be less than GATTS_EXAMPLE_CHAR_VAL_LEN_MAX.
|
||||
if (gatt_db_handle_table[IDX_CHAR_CFG_C_2] == param->write.handle && param->write.len == 2){
|
||||
uint16_t descr_value = param->write.value[1]<<8 | param->write.value[0];
|
||||
uint8_t notify_data[2];
|
||||
notify_data[0] = 0xAA;
|
||||
notify_data[1] = 0xBB;
|
||||
|
||||
if (descr_value == 0x0001){
|
||||
//the size of notify_data[] need less than MTU size
|
||||
esp_ble_gatts_send_indicate(gatts_if, param->write.conn_id, gatt_db_handle_table[IDX_CHAR_VAL_C],
|
||||
sizeof(notify_data), notify_data, false);
|
||||
ESP_LOGI(EXAMPLE_TAG, "(6) ***** send notify AA BB ***** \n");
|
||||
}else if (descr_value == 0x0002){
|
||||
//the size of indicate_data[] need less than MTU size
|
||||
esp_ble_gatts_send_indicate(gatts_if, param->write.conn_id, gatt_db_handle_table[IDX_CHAR_VAL_C],
|
||||
sizeof(notify_data), notify_data, true);
|
||||
}
|
||||
else if (descr_value == 0x0000){
|
||||
ESP_LOGI(EXAMPLE_TAG, "notify/indicate disable ");
|
||||
}else{
|
||||
ESP_LOGE(EXAMPLE_TAG, "unknown descr value");
|
||||
esp_log_buffer_hex(EXAMPLE_TAG, param->write.value, param->write.len);
|
||||
}
|
||||
|
||||
}
|
||||
if(gatt_db_handle_table[IDX_CHAR_VAL_A] == param->write.handle && param->write.len == 2) {
|
||||
uint8_t write_data[2] = {0x88, 0x99};
|
||||
if(memcmp(write_data, param->write.value, param->write.len) == 0) {
|
||||
ESP_LOGI(EXAMPLE_TAG, "(3)***** short write success ***** \n");
|
||||
}
|
||||
}
|
||||
|
||||
/* send response when param->write.need_rsp is true*/
|
||||
if (param->write.need_rsp){
|
||||
esp_ble_gatts_send_response(gatts_if, param->write.conn_id, param->write.trans_id, ESP_GATT_OK, NULL);
|
||||
}
|
||||
}else{
|
||||
/* handle prepare write */
|
||||
example_prepare_write_event_env(gatts_if, &prepare_write_env, param);
|
||||
}
|
||||
break;
|
||||
case ESP_GATTS_EXEC_WRITE_EVT:
|
||||
// the length of gattc prepare write data must be less than GATTS_EXAMPLE_CHAR_VAL_LEN_MAX.
|
||||
ESP_LOGI(EXAMPLE_TAG, "ESP_GATTS_EXEC_WRITE_EVT, Length=%d", prepare_write_env.prepare_len);
|
||||
example_exec_write_event_env(&prepare_write_env, param);
|
||||
break;
|
||||
case ESP_GATTS_MTU_EVT:
|
||||
EXAMPLE_DEBUG(EXAMPLE_TAG, "ESP_GATTS_MTU_EVT, MTU %d", param->mtu.mtu);
|
||||
break;
|
||||
case ESP_GATTS_CONF_EVT:
|
||||
EXAMPLE_DEBUG(EXAMPLE_TAG, "ESP_GATTS_CONF_EVT, status = %d", param->conf.status);
|
||||
break;
|
||||
case ESP_GATTS_START_EVT:
|
||||
EXAMPLE_DEBUG(EXAMPLE_TAG, "SERVICE_START_EVT, status %d, service_handle %d", param->start.status, param->start.service_handle);
|
||||
break;
|
||||
case ESP_GATTS_CONNECT_EVT:
|
||||
ESP_LOGI(EXAMPLE_TAG, "ESP_GATTS_CONNECT_EVT, conn_id = %d", param->connect.conn_id);
|
||||
/* start security connect with peer device when receive the connect event sent by the master */
|
||||
esp_ble_set_encryption(param->connect.remote_bda, ESP_BLE_SEC_ENCRYPT_MITM);
|
||||
break;
|
||||
case ESP_GATTS_DISCONNECT_EVT:
|
||||
ESP_LOGI(EXAMPLE_TAG, "ESP_GATTS_DISCONNECT_EVT, reason = %d", param->disconnect.reason);
|
||||
esp_ble_gap_start_advertising(&adv_params);
|
||||
break;
|
||||
case ESP_GATTS_CREAT_ATTR_TAB_EVT:{
|
||||
if (param->add_attr_tab.status != ESP_GATT_OK){
|
||||
ESP_LOGE(EXAMPLE_TAG, "create attribute table failed, error code=0x%x", param->add_attr_tab.status);
|
||||
}
|
||||
else if (param->add_attr_tab.num_handle != HRS_IDX_NB){
|
||||
ESP_LOGE(EXAMPLE_TAG, "create attribute table abnormally, num_handle (%d) \
|
||||
doesn't equal to HRS_IDX_NB(%d)", param->add_attr_tab.num_handle, HRS_IDX_NB);
|
||||
}
|
||||
else {
|
||||
ESP_LOGI(EXAMPLE_TAG, "create attribute table successfully, the number handle = %d\n",param->add_attr_tab.num_handle);
|
||||
memcpy(gatt_db_handle_table, param->add_attr_tab.handles, sizeof(gatt_db_handle_table));
|
||||
esp_ble_gatts_start_service(gatt_db_handle_table[IDX_SVC]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param)
|
||||
{
|
||||
|
||||
/* If event is register event, store the gatts_if for each profile */
|
||||
if (event == ESP_GATTS_REG_EVT) {
|
||||
if (param->reg.status == ESP_GATT_OK) {
|
||||
heart_rate_profile_tab[PROFILE_APP_IDX].gatts_if = gatts_if;
|
||||
} else {
|
||||
ESP_LOGE(EXAMPLE_TAG, "reg app failed, app_id %04x, status %d",
|
||||
param->reg.app_id,
|
||||
param->reg.status);
|
||||
return;
|
||||
}
|
||||
}
|
||||
do {
|
||||
int idx;
|
||||
for (idx = 0; idx < PROFILE_NUM; idx++) {
|
||||
/* ESP_GATT_IF_NONE, not specify a certain gatt_if, need to call every profile cb function */
|
||||
if (gatts_if == ESP_GATT_IF_NONE || gatts_if == heart_rate_profile_tab[idx].gatts_if) {
|
||||
if (heart_rate_profile_tab[idx].gatts_cb) {
|
||||
heart_rate_profile_tab[idx].gatts_cb(event, gatts_if, param);
|
||||
}
|
||||
}
|
||||
}
|
||||
} while (0);
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
esp_err_t ret;
|
||||
|
||||
/* Initialize NVS. */
|
||||
ret = nvs_flash_init();
|
||||
if (ret == ESP_ERR_NVS_NO_FREE_PAGES) {
|
||||
ESP_ERROR_CHECK(nvs_flash_erase());
|
||||
ret = nvs_flash_init();
|
||||
}
|
||||
ESP_ERROR_CHECK( ret );
|
||||
|
||||
ESP_ERROR_CHECK(nvs_flash_erase());
|
||||
|
||||
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();
|
||||
ret = esp_bt_controller_init(&bt_cfg);
|
||||
if (ret) {
|
||||
ESP_LOGE(EXAMPLE_TAG, "%s enable controller failed: %s", __func__, esp_err_to_name(ret));
|
||||
return;
|
||||
}
|
||||
|
||||
ret = esp_bt_controller_enable(ESP_BT_MODE_BLE);
|
||||
if (ret) {
|
||||
ESP_LOGE(EXAMPLE_TAG, "%s enable controller failed: %s", __func__, esp_err_to_name(ret));
|
||||
return;
|
||||
}
|
||||
|
||||
ret = esp_bluedroid_init();
|
||||
if (ret) {
|
||||
ESP_LOGE(EXAMPLE_TAG, "%s init bluetooth failed: %s", __func__, esp_err_to_name(ret));
|
||||
return;
|
||||
}
|
||||
|
||||
ret = esp_bluedroid_enable();
|
||||
if (ret) {
|
||||
ESP_LOGE(EXAMPLE_TAG, "%s enable bluetooth failed: %s", __func__, esp_err_to_name(ret));
|
||||
return;
|
||||
}
|
||||
|
||||
ret = esp_ble_gatts_register_callback(gatts_event_handler);
|
||||
if (ret){
|
||||
ESP_LOGE(EXAMPLE_TAG, "gatts register error, error code = %x", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
ret = esp_ble_gap_register_callback(gap_event_handler);
|
||||
if (ret){
|
||||
ESP_LOGE(EXAMPLE_TAG, "gap register error, error code = %x", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
ret = esp_ble_gatts_app_register(ESP_APP_ID);
|
||||
if (ret){
|
||||
ESP_LOGE(EXAMPLE_TAG, "gatts app register error, error code = %x", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
esp_err_t local_mtu_ret = esp_ble_gatt_set_local_mtu(33);
|
||||
if (local_mtu_ret){
|
||||
ESP_LOGE(EXAMPLE_TAG, "set local MTU failed, error code = %x", local_mtu_ret);
|
||||
}
|
||||
|
||||
/* set the security iocap & auth_req & key size & init key response key parameters to the stack*/
|
||||
esp_ble_auth_req_t auth_req = ESP_LE_AUTH_REQ_SC_MITM_BOND; //bonding with peer device after authentication
|
||||
esp_ble_io_cap_t iocap = ESP_IO_CAP_OUT; //set the IO capability to No output No input
|
||||
uint8_t key_size = 16; //the key size should be 7~16 bytes
|
||||
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;
|
||||
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));
|
||||
/* If your BLE device act as a Slave, the init_key means you hope which types of key of the master should distribute to you,
|
||||
and the response key means which key you can distribute to the Master;
|
||||
If your BLE device act as a master, the response key means you hope which types of key of the slave should distribute to you,
|
||||
and the init key means which key you can distribute to the slave. */
|
||||
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,33 @@
|
||||
/*
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
|
||||
/* Attributes State Machine */
|
||||
enum
|
||||
{
|
||||
IDX_SVC,
|
||||
IDX_CHAR_A,
|
||||
IDX_CHAR_VAL_A,
|
||||
IDX_CHAR_CFG_A,
|
||||
|
||||
IDX_CHAR_B,
|
||||
IDX_CHAR_VAL_B,
|
||||
IDX_CHAR_CFG_B,
|
||||
|
||||
IDX_CHAR_C,
|
||||
IDX_CHAR_VAL_C,
|
||||
IDX_CHAR_CFG_C,
|
||||
IDX_CHAR_CFG_C_2,
|
||||
|
||||
HRS_IDX_NB,
|
||||
};
|
||||
@@ -0,0 +1,4 @@
|
||||
#
|
||||
# "main" pseudo-component makefile.
|
||||
#
|
||||
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
|
||||
@@ -0,0 +1,14 @@
|
||||
# Override some defaults so BT stack is enabled
|
||||
# in this example
|
||||
|
||||
#
|
||||
# BT config
|
||||
#
|
||||
CONFIG_BT_ENABLED=y
|
||||
|
||||
#
|
||||
# ESP32-specific config
|
||||
#
|
||||
CONFIG_ESP32_ENABLE_STACK_BT=y
|
||||
# CONFIG_ESP32_ENABLE_STACK_NONE is not set
|
||||
CONFIG_MEMMAP_BT=y
|
||||
@@ -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_eddystone_demo)
|
||||
@@ -0,0 +1,11 @@
|
||||
#
|
||||
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
|
||||
# project subdirectory.
|
||||
#
|
||||
|
||||
PROJECT_NAME := ble_eddystone_demo
|
||||
|
||||
COMPONENT_ADD_INCLUDEDIRS := components/include
|
||||
|
||||
include $(IDF_PATH)/make/project.mk
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
| Supported Targets | ESP32 |
|
||||
| ----------------- | ----- |
|
||||
|
||||
ESP-IDF Eddystone demo
|
||||
========================
|
||||
This example demonstrates Eddystone-compatible BLE scanning of eddystone frame,including UID,URL.
|
||||
|
||||
Eddystone is an open beacon protocol specification from Google aimed at improving “proximity-based experiences”
|
||||
with support for both Android and iOS smart device platforms.
|
||||
|
||||
Learn more on https://developers.google.com/beacons and https://github.com/google/eddystone.
|
||||
|
||||
esp_eddystone_protocol.h
|
||||
==========================
|
||||
This header file includes some eddystone-protocol related definitions.
|
||||
|
||||
esp_eddystone_api.h & esp_eddystone_api.c
|
||||
===========================================
|
||||
These files contains the decoding and decoded result of an eddystone frame packet.
|
||||
|
||||
You just need to include esp_eddystone_protocol.h, esp_eddystone_api.h and esp_eddystone_api.c for development.
|
||||
|
||||
esp_eddystone_demo.c
|
||||
======================
|
||||
This is an example/demo of using esp_eddystone_protocol.h, esp_eddystone_api.h and esp_eddystone_api.c files to resolve eddystone packet.
|
||||
@@ -0,0 +1,3 @@
|
||||
idf_component_register(SRCS "esp_eddystone_api.c"
|
||||
"esp_eddystone_demo.c"
|
||||
INCLUDE_DIRS "")
|
||||
@@ -0,0 +1,4 @@
|
||||
#
|
||||
# "main" pseudo-component makefile.
|
||||
#
|
||||
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
|
||||
@@ -0,0 +1,254 @@
|
||||
/*
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
|
||||
/****************************************************************************
|
||||
*
|
||||
* This file is used to decode eddystone information.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "esp_err.h"
|
||||
#include "esp_gap_ble_api.h"
|
||||
#include "esp_eddystone_protocol.h"
|
||||
#include "esp_eddystone_api.h"
|
||||
|
||||
|
||||
/* Declare static functions */
|
||||
static esp_err_t esp_eddystone_uid_received(const uint8_t* buf, uint8_t len, esp_eddystone_result_t* res);
|
||||
static esp_err_t esp_eddystone_url_received(const uint8_t* buf, uint8_t len, esp_eddystone_result_t* res);
|
||||
static char* esp_eddystone_resolve_url_scheme(const uint8_t* url_start, const uint8_t* url_end);
|
||||
static esp_err_t esp_eddystone_tlm_received(const uint8_t* buf, uint8_t len, esp_eddystone_result_t* res);
|
||||
static esp_err_t esp_eddystone_get_inform(const uint8_t* buf, uint8_t len, esp_eddystone_result_t* res);
|
||||
|
||||
/* Eddystone-URL scheme prefixes */
|
||||
static const char* eddystone_url_prefix[4] = {
|
||||
"http://www.",
|
||||
"https://www.",
|
||||
"http://",
|
||||
"https://"
|
||||
};
|
||||
|
||||
/* Eddystone-URL HTTP URL encoding */
|
||||
static const char* eddystone_url_encoding[14] = {
|
||||
".com/",
|
||||
".org/",
|
||||
".edu/",
|
||||
".net/",
|
||||
".info/",
|
||||
".biz/",
|
||||
".gov/",
|
||||
".com",
|
||||
".org",
|
||||
".edu",
|
||||
".net",
|
||||
".info",
|
||||
".biz",
|
||||
".gov"
|
||||
};
|
||||
|
||||
/****************** Eddystone-UID **************
|
||||
Byte offset Field Description
|
||||
0 Frame Type Value = 0x00
|
||||
1 Ranging Data Calibrated Tx power at 0 m
|
||||
2 NID[0] 10-byte Namespace
|
||||
3 NID[1]
|
||||
4 NID[2]
|
||||
5 NID[3]
|
||||
6 NID[4]
|
||||
7 NID[5]
|
||||
8 NID[6]
|
||||
9 NID[7]
|
||||
10 NID[8]
|
||||
11 NID[9]
|
||||
12 BID[0] 6-byte Instance
|
||||
13 BID[1]
|
||||
14 BID[2]
|
||||
15 BID[3]
|
||||
16 BID[4]
|
||||
17 BID[5]
|
||||
18 RFU Reserved for future use, must be0x00
|
||||
19 RFU Reserved for future use, must be0x00
|
||||
*********************************************/
|
||||
/* decode and store received UID */
|
||||
static esp_err_t esp_eddystone_uid_received(const uint8_t* buf, uint8_t len, esp_eddystone_result_t* res)
|
||||
{
|
||||
uint8_t pos = 0;
|
||||
//1-byte Ranging Data + 10-byte Namespace + 6-byte Instance
|
||||
if((len != EDDYSTONE_UID_DATA_LEN) && (len != (EDDYSTONE_UID_RFU_LEN+EDDYSTONE_UID_DATA_LEN))) {
|
||||
//ERROR:uid len wrong
|
||||
return -1;
|
||||
}
|
||||
res->inform.uid.ranging_data = buf[pos++];
|
||||
for(int i=0; i<EDDYSTONE_UID_NAMESPACE_LEN; i++) {
|
||||
res->inform.uid.namespace_id[i] = buf[pos++];
|
||||
}
|
||||
for(int i=0; i<EDDYSTONE_UID_INSTANCE_LEN; i++) {
|
||||
res->inform.uid.instance_id[i] = buf[pos++];
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* resolve received URL to url_res pointer */
|
||||
static char* esp_eddystone_resolve_url_scheme(const uint8_t *url_start, const uint8_t *url_end)
|
||||
{
|
||||
int pos = 0;
|
||||
static char url_buf[100] = {0};
|
||||
const uint8_t *p = url_start;
|
||||
|
||||
pos += sprintf(&url_buf[pos], "%s", eddystone_url_prefix[*p++]);
|
||||
|
||||
for (; p <= url_end; p++) {
|
||||
if (esp_eddystone_is_char_invalid((*p))) {
|
||||
pos += sprintf(&url_buf[pos], "%s", eddystone_url_encoding[*p]);
|
||||
} else {
|
||||
pos += sprintf(&url_buf[pos], "%c", *p);
|
||||
}
|
||||
}
|
||||
return url_buf;
|
||||
}
|
||||
|
||||
/************************** Eddystone-URL *************
|
||||
Frame Specification
|
||||
Byte offset Field Description
|
||||
0 Frame Type Value = 0x10
|
||||
1 TX Power Calibrated Tx power at 0 m
|
||||
2 URL Scheme Encoded Scheme Prefix
|
||||
3+ Encoded URL Length 1-17
|
||||
*******************************************************/
|
||||
/* decode and store received URL, the pointer url_res points to the resolved url */
|
||||
static esp_err_t esp_eddystone_url_received(const uint8_t* buf, uint8_t len, esp_eddystone_result_t* res)
|
||||
{
|
||||
char *url_res = NULL;
|
||||
uint8_t pos = 0;
|
||||
if(len-EDDYSTONE_URL_TX_POWER_LEN > EDDYSTONE_URL_MAX_LEN) {
|
||||
//ERROR:too long url
|
||||
return -1;
|
||||
}
|
||||
res->inform.url.tx_power = buf[pos++];
|
||||
url_res = esp_eddystone_resolve_url_scheme(buf+pos, buf+len-1);
|
||||
memcpy(&res->inform.url.url, url_res, strlen(url_res));
|
||||
res->inform.url.url[strlen(url_res)] = '\0';
|
||||
return 0;
|
||||
}
|
||||
|
||||
/****************** eddystone-tlm ***************
|
||||
* Unencrypted TLM Frame Specification
|
||||
Byte offset Field Description
|
||||
0 Frame Type Value = 0x20
|
||||
1 Version TLM version, value = 0x00
|
||||
2 VBATT[0] Battery voltage, 1 mV/bit
|
||||
3 VBATT[1]
|
||||
4 TEMP[0] Beacon temperature
|
||||
5 TEMP[1]
|
||||
6 ADV_CNT[0] Advertising PDU count
|
||||
7 ADV_CNT[1]
|
||||
8 ADV_CNT[2]
|
||||
9 ADV_CNT[3]
|
||||
10 SEC_CNT[0] Time since power-on or reboot
|
||||
11 SEC_CNT[1]
|
||||
12 SEC_CNT[2]
|
||||
13 SEC_CNT[3]
|
||||
************************************************/
|
||||
/* decode and store received TLM */
|
||||
static esp_err_t esp_eddystone_tlm_received(const uint8_t* buf, uint8_t len, esp_eddystone_result_t* res)
|
||||
{
|
||||
uint8_t pos = 0;
|
||||
if(len > EDDYSTONE_TLM_DATA_LEN) {
|
||||
//ERROR:TLM too long
|
||||
return -1;
|
||||
}
|
||||
res->inform.tlm.version = buf[pos++];
|
||||
res->inform.tlm.battery_voltage = big_endian_read_16(buf, pos);
|
||||
pos += 2;
|
||||
uint16_t temp = big_endian_read_16(buf, pos);
|
||||
int8_t temp_integral = (int8_t)((temp >> 8) & 0xff);
|
||||
float temp_decimal = (temp & 0xff) / 256.0;
|
||||
res->inform.tlm.temperature = temp_integral + temp_decimal;
|
||||
pos += 2;
|
||||
res->inform.tlm.adv_count = big_endian_read_32(buf, pos);
|
||||
pos += 4;
|
||||
res->inform.tlm.time = big_endian_read_32(buf, pos);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static esp_err_t esp_eddystone_get_inform(const uint8_t* buf, uint8_t len, esp_eddystone_result_t* res)
|
||||
{
|
||||
static esp_err_t ret=-1;
|
||||
switch(res->common.frame_type)
|
||||
{
|
||||
case EDDYSTONE_FRAME_TYPE_UID: {
|
||||
ret = esp_eddystone_uid_received(buf, len, res);
|
||||
break;
|
||||
}
|
||||
case EDDYSTONE_FRAME_TYPE_URL: {
|
||||
ret = esp_eddystone_url_received(buf, len, res);
|
||||
break;
|
||||
}
|
||||
case EDDYSTONE_FRAME_TYPE_TLM: {
|
||||
ret = esp_eddystone_tlm_received(buf, len, res);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
esp_err_t esp_eddystone_decode(const uint8_t* buf, uint8_t len, esp_eddystone_result_t* res)
|
||||
{
|
||||
if (len == 0 || buf == NULL || res == NULL) {
|
||||
return -1;
|
||||
}
|
||||
uint8_t pos=0;
|
||||
while(res->common.srv_data_type != EDDYSTONE_SERVICE_UUID)
|
||||
{
|
||||
pos++;
|
||||
if(pos >= len ) {
|
||||
return -1;
|
||||
}
|
||||
uint8_t ad_type = buf[pos++];
|
||||
switch(ad_type)
|
||||
{
|
||||
case ESP_BLE_AD_TYPE_FLAG: {
|
||||
res->common.flags = buf[pos++];
|
||||
break;
|
||||
}
|
||||
case ESP_BLE_AD_TYPE_16SRV_CMPL: {
|
||||
uint16_t uuid = little_endian_read_16(buf, pos);
|
||||
if(uuid != EDDYSTONE_SERVICE_UUID) {
|
||||
return -1;
|
||||
}
|
||||
res->common.srv_uuid = uuid;
|
||||
pos += 2;
|
||||
break;
|
||||
}
|
||||
case ESP_BLE_AD_TYPE_SERVICE_DATA: {
|
||||
uint16_t type = little_endian_read_16(buf, pos);
|
||||
pos += 2;
|
||||
uint8_t frame_type = buf[pos++];
|
||||
if(type != EDDYSTONE_SERVICE_UUID || !(frame_type == EDDYSTONE_FRAME_TYPE_UID || frame_type == EDDYSTONE_FRAME_TYPE_URL ||
|
||||
frame_type == EDDYSTONE_FRAME_TYPE_TLM)) {
|
||||
return -1;
|
||||
}
|
||||
res->common.srv_data_type = type;
|
||||
res->common.frame_type = frame_type;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return esp_eddystone_get_inform(buf+pos, len-pos, res);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef __ESP_EDDYSTONE_API_H__
|
||||
#define __ESP_EDDYSTONE_API_H__
|
||||
|
||||
typedef struct {
|
||||
struct {
|
||||
uint8_t flags; /*<! AD flags data */
|
||||
uint16_t srv_uuid; /*<! complete list of 16-bit service uuid*/
|
||||
uint16_t srv_data_type; /*<! service data type */
|
||||
uint8_t frame_type; /*<! Eddystone UID, URL or TLM */
|
||||
} common;
|
||||
union {
|
||||
struct {
|
||||
/*<! Eddystone-UID */
|
||||
int8_t ranging_data; /*<! calibrated Tx power at 0m */
|
||||
uint8_t namespace_id[10];
|
||||
uint8_t instance_id[6];
|
||||
} uid;
|
||||
struct {
|
||||
/*<! Eddystone-URL */
|
||||
int8_t tx_power; /*<! calibrated Tx power at 0m */
|
||||
char url[EDDYSTONE_URL_MAX_LEN]; /*<! the decoded URL */
|
||||
} url;
|
||||
struct {
|
||||
/*<! Eddystone-TLM */
|
||||
uint8_t version; /*<! TLM version,0x00 for now */
|
||||
uint16_t battery_voltage; /*<! battery voltage in mV */
|
||||
float temperature; /*<! beacon temperature in degrees Celsius */
|
||||
uint32_t adv_count; /*<! adv pdu count since power-up */
|
||||
uint32_t time; /*<! time since power-up, a 0.1 second resolution counter */
|
||||
} tlm;
|
||||
} inform;
|
||||
} esp_eddystone_result_t;
|
||||
|
||||
/* utils */
|
||||
static inline uint16_t little_endian_read_16(const uint8_t *buffer, uint8_t pos)
|
||||
{
|
||||
return ((uint16_t)buffer[pos]) | (((uint16_t)buffer[(pos)+1]) << 8);
|
||||
}
|
||||
|
||||
static inline uint16_t big_endian_read_16(const uint8_t *buffer, uint8_t pos)
|
||||
{
|
||||
return (((uint16_t)buffer[pos]) << 8) | ((uint16_t)buffer[(pos)+1]);
|
||||
}
|
||||
|
||||
static inline uint32_t big_endian_read_32(const uint8_t *buffer, uint8_t pos)
|
||||
{
|
||||
return (((uint32_t)buffer[pos]) << 24) | (((uint32_t)buffer[(pos)+1]) << 16) | (((uint32_t)buffer[(pos)+2]) << 8) | ((uint32_t)buffer[(pos)+3]);
|
||||
}
|
||||
|
||||
/*
|
||||
* The esp eddystone API.
|
||||
* This function is called to decode eddystone information from adv_data.
|
||||
* The res points to the result struct.
|
||||
*
|
||||
*/
|
||||
esp_err_t esp_eddystone_decode(const uint8_t* buf, uint8_t len, esp_eddystone_result_t* res);
|
||||
|
||||
//bool esp_eddystone_is_eddystone_packet(.....);
|
||||
|
||||
#endif /* __ESP_EDDYSTONE_API_H__ */
|
||||
|
||||
@@ -0,0 +1,175 @@
|
||||
/*
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
|
||||
/****************************************************************************
|
||||
*
|
||||
* This file is used for eddystone receiver.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "esp_bt.h"
|
||||
#include "nvs_flash.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_bt_defs.h"
|
||||
#include "esp_bt_main.h"
|
||||
#include "esp_gatt_defs.h"
|
||||
#include "esp_gattc_api.h"
|
||||
#include "esp_gap_ble_api.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
|
||||
#include "esp_eddystone_protocol.h"
|
||||
#include "esp_eddystone_api.h"
|
||||
|
||||
static const char* DEMO_TAG = "EDDYSTONE_DEMO";
|
||||
|
||||
/* declare static functions */
|
||||
static void esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t* param);
|
||||
static void esp_eddystone_show_inform(const esp_eddystone_result_t* res);
|
||||
|
||||
static esp_ble_scan_params_t ble_scan_params = {
|
||||
.scan_type = BLE_SCAN_TYPE_ACTIVE,
|
||||
.own_addr_type = BLE_ADDR_TYPE_PUBLIC,
|
||||
.scan_filter_policy = BLE_SCAN_FILTER_ALLOW_ALL,
|
||||
.scan_interval = 0x50,
|
||||
.scan_window = 0x30,
|
||||
.scan_duplicate = BLE_SCAN_DUPLICATE_DISABLE
|
||||
};
|
||||
|
||||
static void esp_eddystone_show_inform(const esp_eddystone_result_t* res)
|
||||
{
|
||||
switch(res->common.frame_type)
|
||||
{
|
||||
case EDDYSTONE_FRAME_TYPE_UID: {
|
||||
ESP_LOGI(DEMO_TAG, "Eddystone UID inform:");
|
||||
ESP_LOGI(DEMO_TAG, "Measured power(RSSI at 0m distance):%d dbm", res->inform.uid.ranging_data);
|
||||
ESP_LOGI(DEMO_TAG, "EDDYSTONE_DEMO: Namespace ID:0x");
|
||||
esp_log_buffer_hex(DEMO_TAG, res->inform.uid.namespace_id, 10);
|
||||
ESP_LOGI(DEMO_TAG, "EDDYSTONE_DEMO: Instance ID:0x");
|
||||
esp_log_buffer_hex(DEMO_TAG, res->inform.uid.instance_id, 6);
|
||||
break;
|
||||
}
|
||||
case EDDYSTONE_FRAME_TYPE_URL: {
|
||||
ESP_LOGI(DEMO_TAG, "Eddystone URL inform:");
|
||||
ESP_LOGI(DEMO_TAG, "Measured power(RSSI at 0m distance):%d dbm", res->inform.url.tx_power);
|
||||
ESP_LOGI(DEMO_TAG, "URL: %s", res->inform.url.url);
|
||||
break;
|
||||
}
|
||||
case EDDYSTONE_FRAME_TYPE_TLM: {
|
||||
ESP_LOGI(DEMO_TAG, "Eddystone TLM inform:");
|
||||
ESP_LOGI(DEMO_TAG, "version: %d", res->inform.tlm.version);
|
||||
ESP_LOGI(DEMO_TAG, "battery voltage: %d mV", res->inform.tlm.battery_voltage);
|
||||
ESP_LOGI(DEMO_TAG, "beacon temperature in degrees Celsius: %6.1f", res->inform.tlm.temperature);
|
||||
ESP_LOGI(DEMO_TAG, "adv pdu count since power-up: %d", res->inform.tlm.adv_count);
|
||||
ESP_LOGI(DEMO_TAG, "time since power-up: %d s", (res->inform.tlm.time)/10);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t* param)
|
||||
{
|
||||
esp_err_t err;
|
||||
|
||||
switch(event)
|
||||
{
|
||||
case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT: {
|
||||
uint32_t duration = 0;
|
||||
esp_ble_gap_start_scanning(duration);
|
||||
break;
|
||||
}
|
||||
case ESP_GAP_BLE_SCAN_START_COMPLETE_EVT: {
|
||||
if((err = param->scan_start_cmpl.status) != ESP_BT_STATUS_SUCCESS) {
|
||||
ESP_LOGE(DEMO_TAG,"Scan start failed: %s", esp_err_to_name(err));
|
||||
}
|
||||
else {
|
||||
ESP_LOGI(DEMO_TAG,"Start scanning...");
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ESP_GAP_BLE_SCAN_RESULT_EVT: {
|
||||
esp_ble_gap_cb_param_t* scan_result = (esp_ble_gap_cb_param_t*)param;
|
||||
switch(scan_result->scan_rst.search_evt)
|
||||
{
|
||||
case ESP_GAP_SEARCH_INQ_RES_EVT: {
|
||||
esp_eddystone_result_t eddystone_res;
|
||||
memset(&eddystone_res, 0, sizeof(eddystone_res));
|
||||
esp_err_t ret = esp_eddystone_decode(scan_result->scan_rst.ble_adv, scan_result->scan_rst.adv_data_len, &eddystone_res);
|
||||
if (ret) {
|
||||
// error:The received data is not an eddystone frame packet or a correct eddystone frame packet.
|
||||
// just return
|
||||
return;
|
||||
} else {
|
||||
// The received adv data is a correct eddystone frame packet.
|
||||
// Here, we get the eddystone infomation in eddystone_res, we can use the data in res to do other things.
|
||||
// For example, just print them:
|
||||
ESP_LOGI(DEMO_TAG, "--------Eddystone Found----------");
|
||||
esp_log_buffer_hex("EDDYSTONE_DEMO: Device address:", scan_result->scan_rst.bda, ESP_BD_ADDR_LEN);
|
||||
ESP_LOGI(DEMO_TAG, "RSSI of packet:%d dbm", scan_result->scan_rst.rssi);
|
||||
esp_eddystone_show_inform(&eddystone_res);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT:{
|
||||
if((err = param->scan_stop_cmpl.status) != ESP_BT_STATUS_SUCCESS) {
|
||||
ESP_LOGE(DEMO_TAG,"Scan stop failed: %s", esp_err_to_name(err));
|
||||
}
|
||||
else {
|
||||
ESP_LOGI(DEMO_TAG,"Stop scan successfully");
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void esp_eddystone_appRegister(void)
|
||||
{
|
||||
esp_err_t status;
|
||||
|
||||
ESP_LOGI(DEMO_TAG,"Register callback");
|
||||
|
||||
/*<! register the scan callback function to the gap module */
|
||||
if((status = esp_ble_gap_register_callback(esp_gap_cb)) != ESP_OK) {
|
||||
ESP_LOGE(DEMO_TAG,"gap register error: %s", esp_err_to_name(status));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void esp_eddystone_init(void)
|
||||
{
|
||||
esp_bluedroid_init();
|
||||
esp_bluedroid_enable();
|
||||
esp_eddystone_appRegister();
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
ESP_ERROR_CHECK(nvs_flash_init());
|
||||
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_bt_controller_init(&bt_cfg);
|
||||
esp_bt_controller_enable(ESP_BT_MODE_BLE);
|
||||
|
||||
esp_eddystone_init();
|
||||
|
||||
/*<! set scan parameters */
|
||||
esp_ble_gap_set_scan_params(&ble_scan_params);
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
#ifndef __ESP_EDDYSTONE_PROTOCOL_H__
|
||||
#define __ESP_EDDYSTONE_PROTOCOL_H__
|
||||
|
||||
#include "stdbool.h"
|
||||
#include "stdint.h"
|
||||
|
||||
/* Eddystone definitions */
|
||||
#define EDDYSTONE_SERVICE_UUID 0xFEAA
|
||||
|
||||
#define EDDYSTONE_FRAME_TYPE_UID 0x00
|
||||
#define EDDYSTONE_FRAME_TYPE_URL 0x10
|
||||
#define EDDYSTONE_FRAME_TYPE_TLM 0x20
|
||||
#define EDDYSTONE_FRAME_TYPE_EID 0x30
|
||||
//UID
|
||||
#define EDDYSTONE_UID_RANG_DATA_LEN 1
|
||||
#define EDDYSTONE_UID_NAMESPACE_LEN 10
|
||||
#define EDDYSTONE_UID_INSTANCE_LEN 6
|
||||
#define EDDYSTONE_UID_RFU_LEN 2
|
||||
#define EDDYSTONE_UID_DATA_LEN (EDDYSTONE_UID_RANG_DATA_LEN + EDDYSTONE_UID_NAMESPACE_LEN + EDDYSTONE_UID_INSTANCE_LEN)
|
||||
//TLM
|
||||
#define EDDYSTONE_TLM_VERSION_LEN 1
|
||||
#define EDDYSTONE_TLM_BATTERY_VOLTAGE_LEN 2
|
||||
#define EDDYSTONE_TLM_TEMPERATURE_LEN 2
|
||||
#define EDDYSTONE_TLM_ADV_COUNT_LEN 4
|
||||
#define EDDYSTONE_TLM_TIME_LEN 4
|
||||
#define EDDYSTONE_TLM_DATA_LEN (EDDYSTONE_TLM_VERSION_LEN + EDDYSTONE_TLM_BATTERY_VOLTAGE_LEN + \
|
||||
EDDYSTONE_TLM_TEMPERATURE_LEN + EDDYSTONE_TLM_ADV_COUNT_LEN + EDDYSTONE_TLM_TIME_LEN)
|
||||
//URL
|
||||
#define EDDYSTONE_URL_SCHEME_LEN 1
|
||||
#define EDDYSTONE_URL_ENCODED_MAX_LEN 17
|
||||
#define EDDYSTONE_URL_MAX_LEN (EDDYSTONE_URL_SCHEME_LEN + EDDYSTONE_URL_ENCODED_MAX_LEN)
|
||||
#define EDDYSTONE_URL_TX_POWER_LEN 1
|
||||
|
||||
|
||||
/* Eddystone UID frame */
|
||||
typedef struct {
|
||||
int8_t ranging_data; /*<! calibrated Tx power at 0m */
|
||||
uint8_t namespace_id[10];
|
||||
uint8_t instance_id[6];
|
||||
uint8_t reserved[2];
|
||||
} __attribute__((packed))esp_eddystone_uid_t;
|
||||
|
||||
/* Eddystone URL frame */
|
||||
typedef struct {
|
||||
int8_t tx_power; /*<! calibrated Tx power at 0m */
|
||||
uint8_t url_scheme; /*<! encoded scheme prefix */
|
||||
uint8_t encoded_url[0]; /*<! length 1-17 */
|
||||
} __attribute__((packed))esp_eddystone_url_t;
|
||||
|
||||
/* Eddystone TLM frame */
|
||||
typedef struct {
|
||||
uint8_t version; /*<! TLM version,0x00 for now */
|
||||
uint16_t batt; /*<! battery voltage, 1mV/bit */
|
||||
uint16_t temp; /*<! beacon temperature */
|
||||
uint32_t adv_count; /*<! adv pdu count since power-on or reboot */
|
||||
uint32_t time; /*<! time sence power-on or reboot, a 0.1 second resolution counter */
|
||||
} __attribute__((packed)) esp_eddystone_tlm_t;
|
||||
|
||||
/* AD Structure of flags */
|
||||
typedef struct {
|
||||
uint8_t len;
|
||||
uint8_t type;
|
||||
uint8_t flags;
|
||||
} __attribute__((packed)) esp_eddystone_flags_t;
|
||||
|
||||
/* AD Structure of complete 16-bit service uuid */
|
||||
typedef struct {
|
||||
uint8_t len;
|
||||
uint8_t type;
|
||||
uint16_t uuid; /*<! complete list of 16-bit service UUIDs data type value */
|
||||
} __attribute__((packed)) esp_eddystone_uuid_t;
|
||||
|
||||
/* AD Structure of eddystone frame*/
|
||||
typedef struct {
|
||||
uint8_t len; /*<! length of eddystone data */
|
||||
uint8_t type; /*<! service data type,must be 0x16 */
|
||||
uint16_t uuid; /*<! 16-bit eddystone uuid */
|
||||
uint8_t frame_type;
|
||||
union {
|
||||
esp_eddystone_uid_t uid;
|
||||
esp_eddystone_url_t url;
|
||||
esp_eddystone_tlm_t tlm;
|
||||
} u[0];
|
||||
} __attribute__((packed)) esp_eddystone_frame_t;
|
||||
|
||||
/* eddystone packet type */
|
||||
typedef struct {
|
||||
esp_eddystone_flags_t flags;
|
||||
esp_eddystone_uuid_t uuid;
|
||||
esp_eddystone_frame_t frame;
|
||||
} __attribute__((packed)) esp_eddystone_packet_t;
|
||||
|
||||
/*
|
||||
* URLs are written only with the graphic printable characters of the US-ASCII coded character set.
|
||||
* The octets 00-20 and 7F-FF hexadecimal are not used.
|
||||
* See “Excluded US-ASCII Characters” in RFC 2936.
|
||||
*
|
||||
*/
|
||||
static inline bool esp_eddystone_is_char_invalid(int ch)
|
||||
{
|
||||
return (ch >= 0x00 && ch <= 0x20) || (ch >= 0x7f && ch <= 0xff);
|
||||
}
|
||||
|
||||
#endif /* __ESP_EDDYSTONE_PROTOCOL_H__ */
|
||||
@@ -0,0 +1,6 @@
|
||||
# Override some defaults so BT stack is enabled
|
||||
# and WiFi disabled by default in this example
|
||||
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
|
||||
@@ -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(hidd_demos)
|
||||
@@ -0,0 +1,11 @@
|
||||
#
|
||||
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
|
||||
# project subdirectory.
|
||||
#
|
||||
|
||||
PROJECT_NAME := hidd_demos
|
||||
|
||||
COMPONENT_ADD_INCLUDEDIRS := components/include \
|
||||
|
||||
|
||||
include $(IDF_PATH)/make/project.mk
|
||||
@@ -0,0 +1,34 @@
|
||||
| Supported Targets | ESP32 |
|
||||
| ----------------- | ----- |
|
||||
|
||||
ESP-IDF BLE HID device demo
|
||||
========================
|
||||
This example Implemented BLE HID device profile related functions, in which the HID device has
|
||||
4 Reports (1 is mouse, 2 is keyboard and LED, 3 is Consumer Devices, 4 is Vendor devices).
|
||||
Users can choose different reports according to their own application scenarios.
|
||||
BLE HID profile inheritance and USB HID class.
|
||||
|
||||
ble_hidd_demo_main.c
|
||||
==========================
|
||||
This file is the demo to show how to used the HID(you can used it to connected to the smart phone act as the consumer device then can used the button to
|
||||
volume++ or volume-- etc., or connected to the Windows 10 PC act as a keyboard or mouse)
|
||||
|
||||
hidd_le_prf_int.h
|
||||
==========================
|
||||
This header file includes some HID profile related definitions.
|
||||
|
||||
esp_hidd_prf_api.h & esp_hidd_prf_api.c
|
||||
===========================================
|
||||
These files contains the the api of the HID profile
|
||||
|
||||
When you used the HID profile, you just need to added the esp_hidd_prf_api.h includes file and send the HID data used the function defined in the esp_hidd_prf_api.c file.
|
||||
|
||||
hid_dev.h & hid_dev.c
|
||||
======================
|
||||
These file define the HID spec related definitions
|
||||
|
||||
hid_device_le_prf.c
|
||||
======================
|
||||
This file is the HID profile definition file, it include the main function of the HID profile.
|
||||
It mainly includes how to create HID service. If you send and receive HID data and convert the data to keyboard keys,
|
||||
the mouse and consumer values are forwarded to the application.
|
||||
@@ -0,0 +1,7 @@
|
||||
idf_component_register(SRCS "ble_hidd_demo_main.c"
|
||||
"esp_hidd_prf_api.c"
|
||||
"hid_dev.c"
|
||||
"hid_device_le_prf.c"
|
||||
INCLUDE_DIRS ".")
|
||||
|
||||
target_compile_options(${COMPONENT_LIB} PRIVATE -Wno-unused-const-variable)
|
||||
@@ -0,0 +1,251 @@
|
||||
/* This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
Unless required by applicable law or agreed to in writing, this software is
|
||||
distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/event_groups.h"
|
||||
#include "esp_system.h"
|
||||
#include "esp_wifi.h"
|
||||
#include "esp_event.h"
|
||||
#include "esp_log.h"
|
||||
#include "nvs_flash.h"
|
||||
#include "esp_bt.h"
|
||||
|
||||
#include "esp_hidd_prf_api.h"
|
||||
#include "esp_bt_defs.h"
|
||||
#include "esp_gap_ble_api.h"
|
||||
#include "esp_gatts_api.h"
|
||||
#include "esp_gatt_defs.h"
|
||||
#include "esp_bt_main.h"
|
||||
#include "esp_bt_device.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "hid_dev.h"
|
||||
|
||||
/**
|
||||
* Brief:
|
||||
* This example Implemented BLE HID device profile related functions, in which the HID device
|
||||
* has 4 Reports (1 is mouse, 2 is keyboard and LED, 3 is Consumer Devices, 4 is Vendor devices).
|
||||
* Users can choose different reports according to their own application scenarios.
|
||||
* BLE HID profile inheritance and USB HID class.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Note:
|
||||
* 1. Win10 does not support vendor report , So SUPPORT_REPORT_VENDOR is always set to FALSE, it defines in hidd_le_prf_int.h
|
||||
* 2. Update connection parameters are not allowed during iPhone HID encryption, slave turns
|
||||
* off the ability to automatically update connection parameters during encryption.
|
||||
* 3. After our HID device is connected, the iPhones write 1 to the Report Characteristic Configuration Descriptor,
|
||||
* even if the HID encryption is not completed. This should actually be written 1 after the HID encryption is completed.
|
||||
* we modify the permissions of the Report Characteristic Configuration Descriptor to `ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE_ENCRYPTED`.
|
||||
* if you got `GATT_INSUF_ENCRYPTION` error, please ignore.
|
||||
*/
|
||||
|
||||
#define HID_DEMO_TAG "HID_DEMO"
|
||||
|
||||
|
||||
static uint16_t hid_conn_id = 0;
|
||||
static bool sec_conn = false;
|
||||
static bool send_volum_up = false;
|
||||
#define CHAR_DECLARATION_SIZE (sizeof(uint8_t))
|
||||
|
||||
static void hidd_event_callback(esp_hidd_cb_event_t event, esp_hidd_cb_param_t *param);
|
||||
|
||||
#define HIDD_DEVICE_NAME "HID"
|
||||
static uint8_t hidd_service_uuid128[] = {
|
||||
/* LSB <--------------------------------------------------------------------------------> MSB */
|
||||
//first uuid, 16bit, [12],[13] is the value
|
||||
0xfb, 0x34, 0x9b, 0x5f, 0x80, 0x00, 0x00, 0x80, 0x00, 0x10, 0x00, 0x00, 0x12, 0x18, 0x00, 0x00,
|
||||
};
|
||||
|
||||
static esp_ble_adv_data_t hidd_adv_data = {
|
||||
.set_scan_rsp = false,
|
||||
.include_name = true,
|
||||
.include_txpower = true,
|
||||
.min_interval = 0x0006, //slave connection min interval, Time = min_interval * 1.25 msec
|
||||
.max_interval = 0x0010, //slave connection max interval, Time = max_interval * 1.25 msec
|
||||
.appearance = 0x03c0, //HID Generic,
|
||||
.manufacturer_len = 0,
|
||||
.p_manufacturer_data = NULL,
|
||||
.service_data_len = 0,
|
||||
.p_service_data = NULL,
|
||||
.service_uuid_len = sizeof(hidd_service_uuid128),
|
||||
.p_service_uuid = hidd_service_uuid128,
|
||||
.flag = 0x6,
|
||||
};
|
||||
|
||||
static esp_ble_adv_params_t hidd_adv_params = {
|
||||
.adv_int_min = 0x20,
|
||||
.adv_int_max = 0x30,
|
||||
.adv_type = ADV_TYPE_IND,
|
||||
.own_addr_type = BLE_ADDR_TYPE_PUBLIC,
|
||||
//.peer_addr =
|
||||
//.peer_addr_type =
|
||||
.channel_map = ADV_CHNL_ALL,
|
||||
.adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY,
|
||||
};
|
||||
|
||||
|
||||
static void hidd_event_callback(esp_hidd_cb_event_t event, esp_hidd_cb_param_t *param)
|
||||
{
|
||||
switch(event) {
|
||||
case ESP_HIDD_EVENT_REG_FINISH: {
|
||||
if (param->init_finish.state == ESP_HIDD_INIT_OK) {
|
||||
//esp_bd_addr_t rand_addr = {0x04,0x11,0x11,0x11,0x11,0x05};
|
||||
esp_ble_gap_set_device_name(HIDD_DEVICE_NAME);
|
||||
esp_ble_gap_config_adv_data(&hidd_adv_data);
|
||||
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ESP_BAT_EVENT_REG: {
|
||||
break;
|
||||
}
|
||||
case ESP_HIDD_EVENT_DEINIT_FINISH:
|
||||
break;
|
||||
case ESP_HIDD_EVENT_BLE_CONNECT: {
|
||||
ESP_LOGI(HID_DEMO_TAG, "ESP_HIDD_EVENT_BLE_CONNECT");
|
||||
hid_conn_id = param->connect.conn_id;
|
||||
break;
|
||||
}
|
||||
case ESP_HIDD_EVENT_BLE_DISCONNECT: {
|
||||
sec_conn = false;
|
||||
ESP_LOGI(HID_DEMO_TAG, "ESP_HIDD_EVENT_BLE_DISCONNECT");
|
||||
esp_ble_gap_start_advertising(&hidd_adv_params);
|
||||
break;
|
||||
}
|
||||
case ESP_HIDD_EVENT_BLE_VENDOR_REPORT_WRITE_EVT: {
|
||||
ESP_LOGI(HID_DEMO_TAG, "%s, ESP_HIDD_EVENT_BLE_VENDOR_REPORT_WRITE_EVT", __func__);
|
||||
ESP_LOG_BUFFER_HEX(HID_DEMO_TAG, param->vendor_write.data, param->vendor_write.length);
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
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_ADV_DATA_SET_COMPLETE_EVT:
|
||||
esp_ble_gap_start_advertising(&hidd_adv_params);
|
||||
break;
|
||||
case ESP_GAP_BLE_SEC_REQ_EVT:
|
||||
for(int i = 0; i < ESP_BD_ADDR_LEN; i++) {
|
||||
ESP_LOGD(HID_DEMO_TAG, "%x:",param->ble_security.ble_req.bd_addr[i]);
|
||||
}
|
||||
esp_ble_gap_security_rsp(param->ble_security.ble_req.bd_addr, true);
|
||||
break;
|
||||
case ESP_GAP_BLE_AUTH_CMPL_EVT:
|
||||
sec_conn = true;
|
||||
esp_bd_addr_t bd_addr;
|
||||
memcpy(bd_addr, param->ble_security.auth_cmpl.bd_addr, sizeof(esp_bd_addr_t));
|
||||
ESP_LOGI(HID_DEMO_TAG, "remote BD_ADDR: %08x%04x",\
|
||||
(bd_addr[0] << 24) + (bd_addr[1] << 16) + (bd_addr[2] << 8) + bd_addr[3],
|
||||
(bd_addr[4] << 8) + bd_addr[5]);
|
||||
ESP_LOGI(HID_DEMO_TAG, "address type = %d", param->ble_security.auth_cmpl.addr_type);
|
||||
ESP_LOGI(HID_DEMO_TAG, "pair status = %s",param->ble_security.auth_cmpl.success ? "success" : "fail");
|
||||
if(!param->ble_security.auth_cmpl.success) {
|
||||
ESP_LOGE(HID_DEMO_TAG, "fail reason = 0x%x",param->ble_security.auth_cmpl.fail_reason);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void hid_demo_task(void *pvParameters)
|
||||
{
|
||||
vTaskDelay(1000 / portTICK_PERIOD_MS);
|
||||
while(1) {
|
||||
vTaskDelay(2000 / portTICK_PERIOD_MS);
|
||||
if (sec_conn) {
|
||||
ESP_LOGI(HID_DEMO_TAG, "Send the volume");
|
||||
send_volum_up = true;
|
||||
//uint8_t key_vaule = {HID_KEY_A};
|
||||
//esp_hidd_send_keyboard_value(hid_conn_id, 0, &key_vaule, 1);
|
||||
esp_hidd_send_consumer_value(hid_conn_id, HID_CONSUMER_VOLUME_UP, true);
|
||||
vTaskDelay(3000 / portTICK_PERIOD_MS);
|
||||
if (send_volum_up) {
|
||||
send_volum_up = false;
|
||||
esp_hidd_send_consumer_value(hid_conn_id, HID_CONSUMER_VOLUME_UP, false);
|
||||
esp_hidd_send_consumer_value(hid_conn_id, HID_CONSUMER_VOLUME_DOWN, true);
|
||||
vTaskDelay(3000 / portTICK_PERIOD_MS);
|
||||
esp_hidd_send_consumer_value(hid_conn_id, HID_CONSUMER_VOLUME_DOWN, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
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_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT));
|
||||
|
||||
esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
|
||||
ret = esp_bt_controller_init(&bt_cfg);
|
||||
if (ret) {
|
||||
ESP_LOGE(HID_DEMO_TAG, "%s initialize controller failed\n", __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
ret = esp_bt_controller_enable(ESP_BT_MODE_BLE);
|
||||
if (ret) {
|
||||
ESP_LOGE(HID_DEMO_TAG, "%s enable controller failed\n", __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
ret = esp_bluedroid_init();
|
||||
if (ret) {
|
||||
ESP_LOGE(HID_DEMO_TAG, "%s init bluedroid failed\n", __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
ret = esp_bluedroid_enable();
|
||||
if (ret) {
|
||||
ESP_LOGE(HID_DEMO_TAG, "%s init bluedroid failed\n", __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
if((ret = esp_hidd_profile_init()) != ESP_OK) {
|
||||
ESP_LOGE(HID_DEMO_TAG, "%s init bluedroid failed\n", __func__);
|
||||
}
|
||||
|
||||
///register the callback function to the gap module
|
||||
esp_ble_gap_register_callback(gap_event_handler);
|
||||
esp_hidd_register_callbacks(hidd_event_callback);
|
||||
|
||||
/* set the security iocap & auth_req & key size & init key response key parameters to the stack*/
|
||||
esp_ble_auth_req_t auth_req = ESP_LE_AUTH_BOND; //bonding with peer device after authentication
|
||||
esp_ble_io_cap_t iocap = ESP_IO_CAP_NONE; //set the IO capability to No output No input
|
||||
uint8_t key_size = 16; //the key size should be 7~16 bytes
|
||||
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;
|
||||
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));
|
||||
/* If your BLE device act as a Slave, the init_key means you hope which types of key of the master should distribute to you,
|
||||
and the response key means which key you can distribute to the Master;
|
||||
If your BLE device act as a master, the response key means you hope which types of key of the slave should distribute to you,
|
||||
and the init key means which key you can distribute to the slave. */
|
||||
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));
|
||||
|
||||
xTaskCreate(&hid_demo_task, "hid_task", 2048, NULL, 5, NULL);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
#
|
||||
# Main Makefile. This is basically the same as a component makefile.
|
||||
#
|
||||
|
||||
hid_device_le_prf.o: CFLAGS += -Wno-unused-const-variable
|
||||
@@ -0,0 +1,145 @@
|
||||
// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "esp_hidd_prf_api.h"
|
||||
#include "hidd_le_prf_int.h"
|
||||
#include "hid_dev.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "esp_log.h"
|
||||
|
||||
// HID keyboard input report length
|
||||
#define HID_KEYBOARD_IN_RPT_LEN 8
|
||||
|
||||
// HID LED output report length
|
||||
#define HID_LED_OUT_RPT_LEN 1
|
||||
|
||||
// HID mouse input report length
|
||||
#define HID_MOUSE_IN_RPT_LEN 5
|
||||
|
||||
// HID consumer control input report length
|
||||
#define HID_CC_IN_RPT_LEN 2
|
||||
|
||||
esp_err_t esp_hidd_register_callbacks(esp_hidd_event_cb_t callbacks)
|
||||
{
|
||||
esp_err_t hidd_status;
|
||||
|
||||
if(callbacks != NULL) {
|
||||
hidd_le_env.hidd_cb = callbacks;
|
||||
} else {
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
if((hidd_status = hidd_register_cb()) != ESP_OK) {
|
||||
return hidd_status;
|
||||
}
|
||||
|
||||
esp_ble_gatts_app_register(BATTRAY_APP_ID);
|
||||
|
||||
if((hidd_status = esp_ble_gatts_app_register(HIDD_APP_ID)) != ESP_OK) {
|
||||
return hidd_status;
|
||||
}
|
||||
|
||||
return hidd_status;
|
||||
}
|
||||
|
||||
esp_err_t esp_hidd_profile_init(void)
|
||||
{
|
||||
if (hidd_le_env.enabled) {
|
||||
ESP_LOGE(HID_LE_PRF_TAG, "HID device profile already initialized");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
// Reset the hid device target environment
|
||||
memset(&hidd_le_env, 0, sizeof(hidd_le_env_t));
|
||||
hidd_le_env.enabled = true;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t esp_hidd_profile_deinit(void)
|
||||
{
|
||||
uint16_t hidd_svc_hdl = hidd_le_env.hidd_inst.att_tbl[HIDD_LE_IDX_SVC];
|
||||
if (!hidd_le_env.enabled) {
|
||||
ESP_LOGE(HID_LE_PRF_TAG, "HID device profile already initialized");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
if(hidd_svc_hdl != 0) {
|
||||
esp_ble_gatts_stop_service(hidd_svc_hdl);
|
||||
esp_ble_gatts_delete_service(hidd_svc_hdl);
|
||||
} else {
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
/* register the HID device profile to the BTA_GATTS module*/
|
||||
esp_ble_gatts_app_unregister(hidd_le_env.gatt_if);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
uint16_t esp_hidd_get_version(void)
|
||||
{
|
||||
return HIDD_VERSION;
|
||||
}
|
||||
|
||||
void esp_hidd_send_consumer_value(uint16_t conn_id, uint8_t key_cmd, bool key_pressed)
|
||||
{
|
||||
uint8_t buffer[HID_CC_IN_RPT_LEN] = {0, 0};
|
||||
if (key_pressed) {
|
||||
ESP_LOGD(HID_LE_PRF_TAG, "hid_consumer_build_report");
|
||||
hid_consumer_build_report(buffer, key_cmd);
|
||||
}
|
||||
ESP_LOGD(HID_LE_PRF_TAG, "buffer[0] = %x, buffer[1] = %x", buffer[0], buffer[1]);
|
||||
hid_dev_send_report(hidd_le_env.gatt_if, conn_id,
|
||||
HID_RPT_ID_CC_IN, HID_REPORT_TYPE_INPUT, HID_CC_IN_RPT_LEN, buffer);
|
||||
return;
|
||||
}
|
||||
|
||||
void esp_hidd_send_keyboard_value(uint16_t conn_id, key_mask_t special_key_mask, uint8_t *keyboard_cmd, uint8_t num_key)
|
||||
{
|
||||
if (num_key > HID_KEYBOARD_IN_RPT_LEN - 2) {
|
||||
ESP_LOGE(HID_LE_PRF_TAG, "%s(), the number key should not be more than %d", __func__, HID_KEYBOARD_IN_RPT_LEN);
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t buffer[HID_KEYBOARD_IN_RPT_LEN] = {0};
|
||||
|
||||
buffer[0] = special_key_mask;
|
||||
|
||||
for (int i = 0; i < num_key; i++) {
|
||||
buffer[i+2] = keyboard_cmd[i];
|
||||
}
|
||||
|
||||
ESP_LOGD(HID_LE_PRF_TAG, "the key vaule = %d,%d,%d, %d, %d, %d,%d, %d", buffer[0], buffer[1], buffer[2], buffer[3], buffer[4], buffer[5], buffer[6], buffer[7]);
|
||||
hid_dev_send_report(hidd_le_env.gatt_if, conn_id,
|
||||
HID_RPT_ID_KEY_IN, HID_REPORT_TYPE_INPUT, HID_KEYBOARD_IN_RPT_LEN, buffer);
|
||||
return;
|
||||
}
|
||||
|
||||
void esp_hidd_send_mouse_value(uint16_t conn_id, uint8_t mouse_button, int8_t mickeys_x, int8_t mickeys_y)
|
||||
{
|
||||
uint8_t buffer[HID_MOUSE_IN_RPT_LEN];
|
||||
|
||||
buffer[0] = mouse_button; // Buttons
|
||||
buffer[1] = mickeys_x; // X
|
||||
buffer[2] = mickeys_y; // Y
|
||||
buffer[3] = 0; // Wheel
|
||||
buffer[4] = 0; // AC Pan
|
||||
|
||||
hid_dev_send_report(hidd_le_env.gatt_if, conn_id,
|
||||
HID_RPT_ID_MOUSE_IN, HID_REPORT_TYPE_INPUT, HID_MOUSE_IN_RPT_LEN, buffer);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,168 @@
|
||||
// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef __ESP_HIDD_API_H__
|
||||
#define __ESP_HIDD_API_H__
|
||||
|
||||
#include "esp_bt_defs.h"
|
||||
#include "esp_gatt_defs.h"
|
||||
#include "esp_err.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef enum {
|
||||
ESP_HIDD_EVENT_REG_FINISH = 0,
|
||||
ESP_BAT_EVENT_REG,
|
||||
ESP_HIDD_EVENT_DEINIT_FINISH,
|
||||
ESP_HIDD_EVENT_BLE_CONNECT,
|
||||
ESP_HIDD_EVENT_BLE_DISCONNECT,
|
||||
ESP_HIDD_EVENT_BLE_VENDOR_REPORT_WRITE_EVT,
|
||||
} esp_hidd_cb_event_t;
|
||||
|
||||
/// HID config status
|
||||
typedef enum {
|
||||
ESP_HIDD_STA_CONN_SUCCESS = 0x00,
|
||||
ESP_HIDD_STA_CONN_FAIL = 0x01,
|
||||
} esp_hidd_sta_conn_state_t;
|
||||
|
||||
/// HID init status
|
||||
typedef enum {
|
||||
ESP_HIDD_INIT_OK = 0,
|
||||
ESP_HIDD_INIT_FAILED = 1,
|
||||
} esp_hidd_init_state_t;
|
||||
|
||||
/// HID deinit status
|
||||
typedef enum {
|
||||
ESP_HIDD_DEINIT_OK = 0,
|
||||
ESP_HIDD_DEINIT_FAILED = 0,
|
||||
} esp_hidd_deinit_state_t;
|
||||
|
||||
#define LEFT_CONTROL_KEY_MASK (1 << 0)
|
||||
#define LEFT_SHIFT_KEY_MASK (1 << 1)
|
||||
#define LEFT_ALT_KEY_MASK (1 << 2)
|
||||
#define LEFT_GUI_KEY_MASK (1 << 3)
|
||||
#define RIGHT_CONTROL_KEY_MASK (1 << 4)
|
||||
#define RIGHT_SHIFT_KEY_MASK (1 << 5)
|
||||
#define RIGHT_ALT_KEY_MASK (1 << 6)
|
||||
#define RIGHT_GUI_KEY_MASK (1 << 7)
|
||||
|
||||
typedef uint8_t key_mask_t;
|
||||
/**
|
||||
* @brief HIDD callback parameters union
|
||||
*/
|
||||
typedef union {
|
||||
/**
|
||||
* @brief ESP_HIDD_EVENT_INIT_FINISH
|
||||
*/
|
||||
struct hidd_init_finish_evt_param {
|
||||
esp_hidd_init_state_t state; /*!< Initial status */
|
||||
esp_gatt_if_t gatts_if;
|
||||
} init_finish; /*!< HID callback param of ESP_HIDD_EVENT_INIT_FINISH */
|
||||
|
||||
/**
|
||||
* @brief ESP_HIDD_EVENT_DEINIT_FINISH
|
||||
*/
|
||||
struct hidd_deinit_finish_evt_param {
|
||||
esp_hidd_deinit_state_t state; /*!< De-initial status */
|
||||
} deinit_finish; /*!< HID callback param of ESP_HIDD_EVENT_DEINIT_FINISH */
|
||||
|
||||
/**
|
||||
* @brief ESP_HIDD_EVENT_CONNECT
|
||||
*/
|
||||
struct hidd_connect_evt_param {
|
||||
uint16_t conn_id;
|
||||
esp_bd_addr_t remote_bda; /*!< HID Remote bluetooth connection index */
|
||||
} connect; /*!< HID callback param of ESP_HIDD_EVENT_CONNECT */
|
||||
|
||||
/**
|
||||
* @brief ESP_HIDD_EVENT_DISCONNECT
|
||||
*/
|
||||
struct hidd_disconnect_evt_param {
|
||||
esp_bd_addr_t remote_bda; /*!< HID Remote bluetooth device address */
|
||||
} disconnect; /*!< HID callback param of ESP_HIDD_EVENT_DISCONNECT */
|
||||
|
||||
/**
|
||||
* @brief ESP_HIDD_EVENT_BLE_VENDOR_REPORT_WRITE_EVT
|
||||
*/
|
||||
struct hidd_vendor_write_evt_param {
|
||||
uint16_t conn_id; /*!< HID connection index */
|
||||
uint16_t report_id; /*!< HID report index */
|
||||
uint16_t length; /*!< data length */
|
||||
uint8_t *data; /*!< The pointer to the data */
|
||||
} vendor_write; /*!< HID callback param of ESP_HIDD_EVENT_BLE_VENDOR_REPORT_WRITE_EVT */
|
||||
|
||||
} esp_hidd_cb_param_t;
|
||||
|
||||
|
||||
/**
|
||||
* @brief HID device event callback function type
|
||||
* @param event : Event type
|
||||
* @param param : Point to callback parameter, currently is union type
|
||||
*/
|
||||
typedef void (*esp_hidd_event_cb_t) (esp_hidd_cb_event_t event, esp_hidd_cb_param_t *param);
|
||||
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @brief This function is called to receive hid device callback event
|
||||
*
|
||||
* @param[in] callbacks: callback functions
|
||||
*
|
||||
* @return ESP_OK - success, other - failed
|
||||
*
|
||||
*/
|
||||
esp_err_t esp_hidd_register_callbacks(esp_hidd_event_cb_t callbacks);
|
||||
|
||||
/**
|
||||
*
|
||||
* @brief This function is called to initialize hid device profile
|
||||
*
|
||||
* @return ESP_OK - success, other - failed
|
||||
*
|
||||
*/
|
||||
esp_err_t esp_hidd_profile_init(void);
|
||||
|
||||
/**
|
||||
*
|
||||
* @brief This function is called to de-initialize hid device profile
|
||||
*
|
||||
* @return ESP_OK - success, other - failed
|
||||
*
|
||||
*/
|
||||
esp_err_t esp_hidd_profile_deinit(void);
|
||||
|
||||
/**
|
||||
*
|
||||
* @brief Get hidd profile version
|
||||
*
|
||||
* @return Most 8bit significant is Great version, Least 8bit is Sub version
|
||||
*
|
||||
*/
|
||||
uint16_t esp_hidd_get_version(void);
|
||||
|
||||
void esp_hidd_send_consumer_value(uint16_t conn_id, uint8_t key_cmd, bool key_pressed);
|
||||
|
||||
void esp_hidd_send_keyboard_value(uint16_t conn_id, key_mask_t special_key_mask, uint8_t *keyboard_cmd, uint8_t num_key);
|
||||
|
||||
void esp_hidd_send_mouse_value(uint16_t conn_id, uint8_t mouse_button, int8_t mickeys_x, int8_t mickeys_y);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* __ESP_HIDD_API_H__ */
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "hid_dev.h"
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include "esp_log.h"
|
||||
|
||||
static hid_report_map_t *hid_dev_rpt_tbl;
|
||||
static uint8_t hid_dev_rpt_tbl_Len;
|
||||
|
||||
static hid_report_map_t *hid_dev_rpt_by_id(uint8_t id, uint8_t type)
|
||||
{
|
||||
hid_report_map_t *rpt = hid_dev_rpt_tbl;
|
||||
|
||||
for (uint8_t i = hid_dev_rpt_tbl_Len; i > 0; i--, rpt++) {
|
||||
if (rpt->id == id && rpt->type == type && rpt->mode == hidProtocolMode) {
|
||||
return rpt;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void hid_dev_register_reports(uint8_t num_reports, hid_report_map_t *p_report)
|
||||
{
|
||||
hid_dev_rpt_tbl = p_report;
|
||||
hid_dev_rpt_tbl_Len = num_reports;
|
||||
return;
|
||||
}
|
||||
|
||||
void hid_dev_send_report(esp_gatt_if_t gatts_if, uint16_t conn_id,
|
||||
uint8_t id, uint8_t type, uint8_t length, uint8_t *data)
|
||||
{
|
||||
hid_report_map_t *p_rpt;
|
||||
|
||||
// get att handle for report
|
||||
if ((p_rpt = hid_dev_rpt_by_id(id, type)) != NULL) {
|
||||
// if notifications are enabled
|
||||
ESP_LOGD(HID_LE_PRF_TAG, "%s(), send the report, handle = %d", __func__, p_rpt->handle);
|
||||
esp_ble_gatts_send_indicate(gatts_if, conn_id, p_rpt->handle, length, data, false);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
void hid_consumer_build_report(uint8_t *buffer, consumer_cmd_t cmd)
|
||||
{
|
||||
if (!buffer) {
|
||||
ESP_LOGE(HID_LE_PRF_TAG, "%s(), the buffer is NULL, hid build report failed.", __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (cmd) {
|
||||
case HID_CONSUMER_CHANNEL_UP:
|
||||
HID_CC_RPT_SET_CHANNEL(buffer, HID_CC_RPT_CHANNEL_UP);
|
||||
break;
|
||||
|
||||
case HID_CONSUMER_CHANNEL_DOWN:
|
||||
HID_CC_RPT_SET_CHANNEL(buffer, HID_CC_RPT_CHANNEL_DOWN);
|
||||
break;
|
||||
|
||||
case HID_CONSUMER_VOLUME_UP:
|
||||
HID_CC_RPT_SET_VOLUME_UP(buffer);
|
||||
break;
|
||||
|
||||
case HID_CONSUMER_VOLUME_DOWN:
|
||||
HID_CC_RPT_SET_VOLUME_DOWN(buffer);
|
||||
break;
|
||||
|
||||
case HID_CONSUMER_MUTE:
|
||||
HID_CC_RPT_SET_BUTTON(buffer, HID_CC_RPT_MUTE);
|
||||
break;
|
||||
|
||||
case HID_CONSUMER_POWER:
|
||||
HID_CC_RPT_SET_BUTTON(buffer, HID_CC_RPT_POWER);
|
||||
break;
|
||||
|
||||
case HID_CONSUMER_RECALL_LAST:
|
||||
HID_CC_RPT_SET_BUTTON(buffer, HID_CC_RPT_LAST);
|
||||
break;
|
||||
|
||||
case HID_CONSUMER_ASSIGN_SEL:
|
||||
HID_CC_RPT_SET_BUTTON(buffer, HID_CC_RPT_ASSIGN_SEL);
|
||||
break;
|
||||
|
||||
case HID_CONSUMER_PLAY:
|
||||
HID_CC_RPT_SET_BUTTON(buffer, HID_CC_RPT_PLAY);
|
||||
break;
|
||||
|
||||
case HID_CONSUMER_PAUSE:
|
||||
HID_CC_RPT_SET_BUTTON(buffer, HID_CC_RPT_PAUSE);
|
||||
break;
|
||||
|
||||
case HID_CONSUMER_RECORD:
|
||||
HID_CC_RPT_SET_BUTTON(buffer, HID_CC_RPT_RECORD);
|
||||
break;
|
||||
|
||||
case HID_CONSUMER_FAST_FORWARD:
|
||||
HID_CC_RPT_SET_BUTTON(buffer, HID_CC_RPT_FAST_FWD);
|
||||
break;
|
||||
|
||||
case HID_CONSUMER_REWIND:
|
||||
HID_CC_RPT_SET_BUTTON(buffer, HID_CC_RPT_REWIND);
|
||||
break;
|
||||
|
||||
case HID_CONSUMER_SCAN_NEXT_TRK:
|
||||
HID_CC_RPT_SET_BUTTON(buffer, HID_CC_RPT_SCAN_NEXT_TRK);
|
||||
break;
|
||||
|
||||
case HID_CONSUMER_SCAN_PREV_TRK:
|
||||
HID_CC_RPT_SET_BUTTON(buffer, HID_CC_RPT_SCAN_PREV_TRK);
|
||||
break;
|
||||
|
||||
case HID_CONSUMER_STOP:
|
||||
HID_CC_RPT_SET_BUTTON(buffer, HID_CC_RPT_STOP);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,261 @@
|
||||
// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef HID_DEV_H__
|
||||
#define HID_DEV_H__
|
||||
|
||||
#include "hidd_le_prf_int.h"
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
|
||||
/* HID Report type */
|
||||
#define HID_TYPE_INPUT 1
|
||||
#define HID_TYPE_OUTPUT 2
|
||||
#define HID_TYPE_FEATURE 3
|
||||
|
||||
// HID Keyboard/Keypad Usage IDs (subset of the codes available in the USB HID Usage Tables spec)
|
||||
#define HID_KEY_RESERVED 0 // No event inidicated
|
||||
#define HID_KEY_A 4 // Keyboard a and A
|
||||
#define HID_KEY_B 5 // Keyboard b and B
|
||||
#define HID_KEY_C 6 // Keyboard c and C
|
||||
#define HID_KEY_D 7 // Keyboard d and D
|
||||
#define HID_KEY_E 8 // Keyboard e and E
|
||||
#define HID_KEY_F 9 // Keyboard f and F
|
||||
#define HID_KEY_G 10 // Keyboard g and G
|
||||
#define HID_KEY_H 11 // Keyboard h and H
|
||||
#define HID_KEY_I 12 // Keyboard i and I
|
||||
#define HID_KEY_J 13 // Keyboard j and J
|
||||
#define HID_KEY_K 14 // Keyboard k and K
|
||||
#define HID_KEY_L 15 // Keyboard l and L
|
||||
#define HID_KEY_M 16 // Keyboard m and M
|
||||
#define HID_KEY_N 17 // Keyboard n and N
|
||||
#define HID_KEY_O 18 // Keyboard o and O
|
||||
#define HID_KEY_P 19 // Keyboard p and p
|
||||
#define HID_KEY_Q 20 // Keyboard q and Q
|
||||
#define HID_KEY_R 21 // Keyboard r and R
|
||||
#define HID_KEY_S 22 // Keyboard s and S
|
||||
#define HID_KEY_T 23 // Keyboard t and T
|
||||
#define HID_KEY_U 24 // Keyboard u and U
|
||||
#define HID_KEY_V 25 // Keyboard v and V
|
||||
#define HID_KEY_W 26 // Keyboard w and W
|
||||
#define HID_KEY_X 27 // Keyboard x and X
|
||||
#define HID_KEY_Y 28 // Keyboard y and Y
|
||||
#define HID_KEY_Z 29 // Keyboard z and Z
|
||||
#define HID_KEY_1 30 // Keyboard 1 and !
|
||||
#define HID_KEY_2 31 // Keyboard 2 and @
|
||||
#define HID_KEY_3 32 // Keyboard 3 and #
|
||||
#define HID_KEY_4 33 // Keyboard 4 and %
|
||||
#define HID_KEY_5 34 // Keyboard 5 and %
|
||||
#define HID_KEY_6 35 // Keyboard 6 and ^
|
||||
#define HID_KEY_7 36 // Keyboard 7 and &
|
||||
#define HID_KEY_8 37 // Keyboard 8 and *
|
||||
#define HID_KEY_9 38 // Keyboard 9 and (
|
||||
#define HID_KEY_0 39 // Keyboard 0 and )
|
||||
#define HID_KEY_RETURN 40 // Keyboard Return (ENTER)
|
||||
#define HID_KEY_ESCAPE 41 // Keyboard ESCAPE
|
||||
#define HID_KEY_DELETE 42 // Keyboard DELETE (Backspace)
|
||||
#define HID_KEY_TAB 43 // Keyboard Tab
|
||||
#define HID_KEY_SPACEBAR 44 // Keyboard Spacebar
|
||||
#define HID_KEY_MINUS 45 // Keyboard - and (underscore)
|
||||
#define HID_KEY_EQUAL 46 // Keyboard = and +
|
||||
#define HID_KEY_LEFT_BRKT 47 // Keyboard [ and {
|
||||
#define HID_KEY_RIGHT_BRKT 48 // Keyboard ] and }
|
||||
#define HID_KEY_BACK_SLASH 49 // Keyboard \ and |
|
||||
#define HID_KEY_SEMI_COLON 51 // Keyboard ; and :
|
||||
#define HID_KEY_SGL_QUOTE 52 // Keyboard ' and "
|
||||
#define HID_KEY_GRV_ACCENT 53 // Keyboard Grave Accent and Tilde
|
||||
#define HID_KEY_COMMA 54 // Keyboard , and <
|
||||
#define HID_KEY_DOT 55 // Keyboard . and >
|
||||
#define HID_KEY_FWD_SLASH 56 // Keyboard / and ?
|
||||
#define HID_KEY_CAPS_LOCK 57 // Keyboard Caps Lock
|
||||
#define HID_KEY_F1 58 // Keyboard F1
|
||||
#define HID_KEY_F2 59 // Keyboard F2
|
||||
#define HID_KEY_F3 60 // Keyboard F3
|
||||
#define HID_KEY_F4 61 // Keyboard F4
|
||||
#define HID_KEY_F5 62 // Keyboard F5
|
||||
#define HID_KEY_F6 63 // Keyboard F6
|
||||
#define HID_KEY_F7 64 // Keyboard F7
|
||||
#define HID_KEY_F8 65 // Keyboard F8
|
||||
#define HID_KEY_F9 66 // Keyboard F9
|
||||
#define HID_KEY_F10 67 // Keyboard F10
|
||||
#define HID_KEY_F11 68 // Keyboard F11
|
||||
#define HID_KEY_F12 69 // Keyboard F12
|
||||
#define HID_KEY_PRNT_SCREEN 70 // Keyboard Print Screen
|
||||
#define HID_KEY_SCROLL_LOCK 71 // Keyboard Scroll Lock
|
||||
#define HID_KEY_PAUSE 72 // Keyboard Pause
|
||||
#define HID_KEY_INSERT 73 // Keyboard Insert
|
||||
#define HID_KEY_HOME 74 // Keyboard Home
|
||||
#define HID_KEY_PAGE_UP 75 // Keyboard PageUp
|
||||
#define HID_KEY_DELETE_FWD 76 // Keyboard Delete Forward
|
||||
#define HID_KEY_END 77 // Keyboard End
|
||||
#define HID_KEY_PAGE_DOWN 78 // Keyboard PageDown
|
||||
#define HID_KEY_RIGHT_ARROW 79 // Keyboard RightArrow
|
||||
#define HID_KEY_LEFT_ARROW 80 // Keyboard LeftArrow
|
||||
#define HID_KEY_DOWN_ARROW 81 // Keyboard DownArrow
|
||||
#define HID_KEY_UP_ARROW 82 // Keyboard UpArrow
|
||||
#define HID_KEY_NUM_LOCK 83 // Keypad Num Lock and Clear
|
||||
#define HID_KEY_DIVIDE 84 // Keypad /
|
||||
#define HID_KEY_MULTIPLY 85 // Keypad *
|
||||
#define HID_KEY_SUBTRACT 86 // Keypad -
|
||||
#define HID_KEY_ADD 87 // Keypad +
|
||||
#define HID_KEY_ENTER 88 // Keypad ENTER
|
||||
#define HID_KEYPAD_1 89 // Keypad 1 and End
|
||||
#define HID_KEYPAD_2 90 // Keypad 2 and Down Arrow
|
||||
#define HID_KEYPAD_3 91 // Keypad 3 and PageDn
|
||||
#define HID_KEYPAD_4 92 // Keypad 4 and Lfet Arrow
|
||||
#define HID_KEYPAD_5 93 // Keypad 5
|
||||
#define HID_KEYPAD_6 94 // Keypad 6 and Right Arrow
|
||||
#define HID_KEYPAD_7 95 // Keypad 7 and Home
|
||||
#define HID_KEYPAD_8 96 // Keypad 8 and Up Arrow
|
||||
#define HID_KEYPAD_9 97 // Keypad 9 and PageUp
|
||||
#define HID_KEYPAD_0 98 // Keypad 0 and Insert
|
||||
#define HID_KEYPAD_DOT 99 // Keypad . and Delete
|
||||
#define HID_KEY_MUTE 127 // Keyboard Mute
|
||||
#define HID_KEY_VOLUME_UP 128 // Keyboard Volume up
|
||||
#define HID_KEY_VOLUME_DOWN 129 // Keyboard Volume down
|
||||
#define HID_KEY_LEFT_CTRL 224 // Keyboard LeftContorl
|
||||
#define HID_KEY_LEFT_SHIFT 225 // Keyboard LeftShift
|
||||
#define HID_KEY_LEFT_ALT 226 // Keyboard LeftAlt
|
||||
#define HID_KEY_LEFT_GUI 227 // Keyboard LeftGUI
|
||||
#define HID_KEY_RIGHT_CTRL 228 // Keyboard LeftContorl
|
||||
#define HID_KEY_RIGHT_SHIFT 229 // Keyboard LeftShift
|
||||
#define HID_KEY_RIGHT_ALT 230 // Keyboard LeftAlt
|
||||
#define HID_KEY_RIGHT_GUI 231 // Keyboard RightGUI
|
||||
typedef uint8_t keyboard_cmd_t;
|
||||
|
||||
#define HID_MOUSE_LEFT 253
|
||||
#define HID_MOUSE_MIDDLE 254
|
||||
#define HID_MOUSE_RIGHT 255
|
||||
typedef uint8_t mouse_cmd_t;
|
||||
|
||||
// HID Consumer Usage IDs (subset of the codes available in the USB HID Usage Tables spec)
|
||||
#define HID_CONSUMER_POWER 48 // Power
|
||||
#define HID_CONSUMER_RESET 49 // Reset
|
||||
#define HID_CONSUMER_SLEEP 50 // Sleep
|
||||
|
||||
#define HID_CONSUMER_MENU 64 // Menu
|
||||
#define HID_CONSUMER_SELECTION 128 // Selection
|
||||
#define HID_CONSUMER_ASSIGN_SEL 129 // Assign Selection
|
||||
#define HID_CONSUMER_MODE_STEP 130 // Mode Step
|
||||
#define HID_CONSUMER_RECALL_LAST 131 // Recall Last
|
||||
#define HID_CONSUMER_QUIT 148 // Quit
|
||||
#define HID_CONSUMER_HELP 149 // Help
|
||||
#define HID_CONSUMER_CHANNEL_UP 156 // Channel Increment
|
||||
#define HID_CONSUMER_CHANNEL_DOWN 157 // Channel Decrement
|
||||
|
||||
#define HID_CONSUMER_PLAY 176 // Play
|
||||
#define HID_CONSUMER_PAUSE 177 // Pause
|
||||
#define HID_CONSUMER_RECORD 178 // Record
|
||||
#define HID_CONSUMER_FAST_FORWARD 179 // Fast Forward
|
||||
#define HID_CONSUMER_REWIND 180 // Rewind
|
||||
#define HID_CONSUMER_SCAN_NEXT_TRK 181 // Scan Next Track
|
||||
#define HID_CONSUMER_SCAN_PREV_TRK 182 // Scan Previous Track
|
||||
#define HID_CONSUMER_STOP 183 // Stop
|
||||
#define HID_CONSUMER_EJECT 184 // Eject
|
||||
#define HID_CONSUMER_RANDOM_PLAY 185 // Random Play
|
||||
#define HID_CONSUMER_SELECT_DISC 186 // Select Disk
|
||||
#define HID_CONSUMER_ENTER_DISC 187 // Enter Disc
|
||||
#define HID_CONSUMER_REPEAT 188 // Repeat
|
||||
#define HID_CONSUMER_STOP_EJECT 204 // Stop/Eject
|
||||
#define HID_CONSUMER_PLAY_PAUSE 205 // Play/Pause
|
||||
#define HID_CONSUMER_PLAY_SKIP 206 // Play/Skip
|
||||
|
||||
#define HID_CONSUMER_VOLUME 224 // Volume
|
||||
#define HID_CONSUMER_BALANCE 225 // Balance
|
||||
#define HID_CONSUMER_MUTE 226 // Mute
|
||||
#define HID_CONSUMER_BASS 227 // Bass
|
||||
#define HID_CONSUMER_VOLUME_UP 233 // Volume Increment
|
||||
#define HID_CONSUMER_VOLUME_DOWN 234 // Volume Decrement
|
||||
typedef uint8_t consumer_cmd_t;
|
||||
|
||||
#define HID_CC_RPT_MUTE 1
|
||||
#define HID_CC_RPT_POWER 2
|
||||
#define HID_CC_RPT_LAST 3
|
||||
#define HID_CC_RPT_ASSIGN_SEL 4
|
||||
#define HID_CC_RPT_PLAY 5
|
||||
#define HID_CC_RPT_PAUSE 6
|
||||
#define HID_CC_RPT_RECORD 7
|
||||
#define HID_CC_RPT_FAST_FWD 8
|
||||
#define HID_CC_RPT_REWIND 9
|
||||
#define HID_CC_RPT_SCAN_NEXT_TRK 10
|
||||
#define HID_CC_RPT_SCAN_PREV_TRK 11
|
||||
#define HID_CC_RPT_STOP 12
|
||||
|
||||
#define HID_CC_RPT_CHANNEL_UP 0x01
|
||||
#define HID_CC_RPT_CHANNEL_DOWN 0x03
|
||||
#define HID_CC_RPT_VOLUME_UP 0x40
|
||||
#define HID_CC_RPT_VOLUME_DOWN 0x80
|
||||
|
||||
// HID Consumer Control report bitmasks
|
||||
#define HID_CC_RPT_NUMERIC_BITS 0xF0
|
||||
#define HID_CC_RPT_CHANNEL_BITS 0xCF
|
||||
#define HID_CC_RPT_VOLUME_BITS 0x3F
|
||||
#define HID_CC_RPT_BUTTON_BITS 0xF0
|
||||
#define HID_CC_RPT_SELECTION_BITS 0xCF
|
||||
|
||||
|
||||
// Macros for the HID Consumer Control 2-byte report
|
||||
#define HID_CC_RPT_SET_NUMERIC(s, x) (s)[0] &= HID_CC_RPT_NUMERIC_BITS; \
|
||||
(s)[0] = (x)
|
||||
#define HID_CC_RPT_SET_CHANNEL(s, x) (s)[0] &= HID_CC_RPT_CHANNEL_BITS; \
|
||||
(s)[0] |= ((x) & 0x03) << 4
|
||||
#define HID_CC_RPT_SET_VOLUME_UP(s) (s)[0] &= HID_CC_RPT_VOLUME_BITS; \
|
||||
(s)[0] |= 0x40
|
||||
#define HID_CC_RPT_SET_VOLUME_DOWN(s) (s)[0] &= HID_CC_RPT_VOLUME_BITS; \
|
||||
(s)[0] |= 0x80
|
||||
#define HID_CC_RPT_SET_BUTTON(s, x) (s)[1] &= HID_CC_RPT_BUTTON_BITS; \
|
||||
(s)[1] |= (x)
|
||||
#define HID_CC_RPT_SET_SELECTION(s, x) (s)[1] &= HID_CC_RPT_SELECTION_BITS; \
|
||||
(s)[1] |= ((x) & 0x03) << 4
|
||||
|
||||
|
||||
// HID report mapping table
|
||||
typedef struct
|
||||
{
|
||||
uint16_t handle; // Handle of report characteristic
|
||||
uint16_t cccdHandle; // Handle of CCCD for report characteristic
|
||||
uint8_t id; // Report ID
|
||||
uint8_t type; // Report type
|
||||
uint8_t mode; // Protocol mode (report or boot)
|
||||
} hid_report_map_t;
|
||||
|
||||
// HID dev configuration structure
|
||||
typedef struct
|
||||
{
|
||||
uint32_t idleTimeout; // Idle timeout in milliseconds
|
||||
uint8_t hidFlags; // HID feature flags
|
||||
|
||||
} hid_dev_cfg_t;
|
||||
|
||||
void hid_dev_register_reports(uint8_t num_reports, hid_report_map_t *p_report);
|
||||
|
||||
void hid_dev_send_report(esp_gatt_if_t gatts_if, uint16_t conn_id,
|
||||
uint8_t id, uint8_t type, uint8_t length, uint8_t *data);
|
||||
|
||||
void hid_consumer_build_report(uint8_t *buffer, consumer_cmd_t cmd);
|
||||
|
||||
void hid_keyboard_build_report(uint8_t *buffer, keyboard_cmd_t cmd);
|
||||
|
||||
void hid_mouse_build_report(uint8_t *buffer, mouse_cmd_t cmd);
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
|
||||
#endif /* HID_DEV_H__ */
|
||||
|
||||
@@ -0,0 +1,816 @@
|
||||
// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "hidd_le_prf_int.h"
|
||||
#include <string.h>
|
||||
#include "esp_log.h"
|
||||
|
||||
/// characteristic presentation information
|
||||
struct prf_char_pres_fmt
|
||||
{
|
||||
/// Unit (The Unit is a UUID)
|
||||
uint16_t unit;
|
||||
/// Description
|
||||
uint16_t description;
|
||||
/// Format
|
||||
uint8_t format;
|
||||
/// Exponent
|
||||
uint8_t exponent;
|
||||
/// Name space
|
||||
uint8_t name_space;
|
||||
};
|
||||
|
||||
// HID report mapping table
|
||||
static hid_report_map_t hid_rpt_map[HID_NUM_REPORTS];
|
||||
|
||||
// HID Report Map characteristic value
|
||||
// Keyboard report descriptor (using format for Boot interface descriptor)
|
||||
static const uint8_t hidReportMap[] = {
|
||||
0x05, 0x01, // Usage Page (Generic Desktop)
|
||||
0x09, 0x02, // Usage (Mouse)
|
||||
0xA1, 0x01, // Collection (Application)
|
||||
0x85, 0x01, // Report Id (1)
|
||||
0x09, 0x01, // Usage (Pointer)
|
||||
0xA1, 0x00, // Collection (Physical)
|
||||
0x05, 0x09, // Usage Page (Buttons)
|
||||
0x19, 0x01, // Usage Minimum (01) - Button 1
|
||||
0x29, 0x03, // Usage Maximum (03) - Button 3
|
||||
0x15, 0x00, // Logical Minimum (0)
|
||||
0x25, 0x01, // Logical Maximum (1)
|
||||
0x75, 0x01, // Report Size (1)
|
||||
0x95, 0x03, // Report Count (3)
|
||||
0x81, 0x02, // Input (Data, Variable, Absolute) - Button states
|
||||
0x75, 0x05, // Report Size (5)
|
||||
0x95, 0x01, // Report Count (1)
|
||||
0x81, 0x01, // Input (Constant) - Padding or Reserved bits
|
||||
0x05, 0x01, // Usage Page (Generic Desktop)
|
||||
0x09, 0x30, // Usage (X)
|
||||
0x09, 0x31, // Usage (Y)
|
||||
0x09, 0x38, // Usage (Wheel)
|
||||
0x15, 0x81, // Logical Minimum (-127)
|
||||
0x25, 0x7F, // Logical Maximum (127)
|
||||
0x75, 0x08, // Report Size (8)
|
||||
0x95, 0x03, // Report Count (3)
|
||||
0x81, 0x06, // Input (Data, Variable, Relative) - X & Y coordinate
|
||||
0xC0, // End Collection
|
||||
0xC0, // End Collection
|
||||
|
||||
0x05, 0x01, // Usage Pg (Generic Desktop)
|
||||
0x09, 0x06, // Usage (Keyboard)
|
||||
0xA1, 0x01, // Collection: (Application)
|
||||
0x85, 0x02, // Report Id (2)
|
||||
//
|
||||
0x05, 0x07, // Usage Pg (Key Codes)
|
||||
0x19, 0xE0, // Usage Min (224)
|
||||
0x29, 0xE7, // Usage Max (231)
|
||||
0x15, 0x00, // Log Min (0)
|
||||
0x25, 0x01, // Log Max (1)
|
||||
//
|
||||
// Modifier byte
|
||||
0x75, 0x01, // Report Size (1)
|
||||
0x95, 0x08, // Report Count (8)
|
||||
0x81, 0x02, // Input: (Data, Variable, Absolute)
|
||||
//
|
||||
// Reserved byte
|
||||
0x95, 0x01, // Report Count (1)
|
||||
0x75, 0x08, // Report Size (8)
|
||||
0x81, 0x01, // Input: (Constant)
|
||||
//
|
||||
// LED report
|
||||
0x95, 0x05, // Report Count (5)
|
||||
0x75, 0x01, // Report Size (1)
|
||||
0x05, 0x08, // Usage Pg (LEDs)
|
||||
0x19, 0x01, // Usage Min (1)
|
||||
0x29, 0x05, // Usage Max (5)
|
||||
0x91, 0x02, // Output: (Data, Variable, Absolute)
|
||||
//
|
||||
// LED report padding
|
||||
0x95, 0x01, // Report Count (1)
|
||||
0x75, 0x03, // Report Size (3)
|
||||
0x91, 0x01, // Output: (Constant)
|
||||
//
|
||||
// Key arrays (6 bytes)
|
||||
0x95, 0x06, // Report Count (6)
|
||||
0x75, 0x08, // Report Size (8)
|
||||
0x15, 0x00, // Log Min (0)
|
||||
0x25, 0x65, // Log Max (101)
|
||||
0x05, 0x07, // Usage Pg (Key Codes)
|
||||
0x19, 0x00, // Usage Min (0)
|
||||
0x29, 0x65, // Usage Max (101)
|
||||
0x81, 0x00, // Input: (Data, Array)
|
||||
//
|
||||
0xC0, // End Collection
|
||||
//
|
||||
0x05, 0x0C, // Usage Pg (Consumer Devices)
|
||||
0x09, 0x01, // Usage (Consumer Control)
|
||||
0xA1, 0x01, // Collection (Application)
|
||||
0x85, 0x03, // Report Id (3)
|
||||
0x09, 0x02, // Usage (Numeric Key Pad)
|
||||
0xA1, 0x02, // Collection (Logical)
|
||||
0x05, 0x09, // Usage Pg (Button)
|
||||
0x19, 0x01, // Usage Min (Button 1)
|
||||
0x29, 0x0A, // Usage Max (Button 10)
|
||||
0x15, 0x01, // Logical Min (1)
|
||||
0x25, 0x0A, // Logical Max (10)
|
||||
0x75, 0x04, // Report Size (4)
|
||||
0x95, 0x01, // Report Count (1)
|
||||
0x81, 0x00, // Input (Data, Ary, Abs)
|
||||
0xC0, // End Collection
|
||||
0x05, 0x0C, // Usage Pg (Consumer Devices)
|
||||
0x09, 0x86, // Usage (Channel)
|
||||
0x15, 0xFF, // Logical Min (-1)
|
||||
0x25, 0x01, // Logical Max (1)
|
||||
0x75, 0x02, // Report Size (2)
|
||||
0x95, 0x01, // Report Count (1)
|
||||
0x81, 0x46, // Input (Data, Var, Rel, Null)
|
||||
0x09, 0xE9, // Usage (Volume Up)
|
||||
0x09, 0xEA, // Usage (Volume Down)
|
||||
0x15, 0x00, // Logical Min (0)
|
||||
0x75, 0x01, // Report Size (1)
|
||||
0x95, 0x02, // Report Count (2)
|
||||
0x81, 0x02, // Input (Data, Var, Abs)
|
||||
0x09, 0xE2, // Usage (Mute)
|
||||
0x09, 0x30, // Usage (Power)
|
||||
0x09, 0x83, // Usage (Recall Last)
|
||||
0x09, 0x81, // Usage (Assign Selection)
|
||||
0x09, 0xB0, // Usage (Play)
|
||||
0x09, 0xB1, // Usage (Pause)
|
||||
0x09, 0xB2, // Usage (Record)
|
||||
0x09, 0xB3, // Usage (Fast Forward)
|
||||
0x09, 0xB4, // Usage (Rewind)
|
||||
0x09, 0xB5, // Usage (Scan Next)
|
||||
0x09, 0xB6, // Usage (Scan Prev)
|
||||
0x09, 0xB7, // Usage (Stop)
|
||||
0x15, 0x01, // Logical Min (1)
|
||||
0x25, 0x0C, // Logical Max (12)
|
||||
0x75, 0x04, // Report Size (4)
|
||||
0x95, 0x01, // Report Count (1)
|
||||
0x81, 0x00, // Input (Data, Ary, Abs)
|
||||
0x09, 0x80, // Usage (Selection)
|
||||
0xA1, 0x02, // Collection (Logical)
|
||||
0x05, 0x09, // Usage Pg (Button)
|
||||
0x19, 0x01, // Usage Min (Button 1)
|
||||
0x29, 0x03, // Usage Max (Button 3)
|
||||
0x15, 0x01, // Logical Min (1)
|
||||
0x25, 0x03, // Logical Max (3)
|
||||
0x75, 0x02, // Report Size (2)
|
||||
0x81, 0x00, // Input (Data, Ary, Abs)
|
||||
0xC0, // End Collection
|
||||
0x81, 0x03, // Input (Const, Var, Abs)
|
||||
0xC0, // End Collectionq
|
||||
|
||||
#if (SUPPORT_REPORT_VENDOR == true)
|
||||
0x06, 0xFF, 0xFF, // Usage Page(Vendor defined)
|
||||
0x09, 0xA5, // Usage(Vendor Defined)
|
||||
0xA1, 0x01, // Collection(Application)
|
||||
0x85, 0x04, // Report Id (4)
|
||||
0x09, 0xA6, // Usage(Vendor defined)
|
||||
0x09, 0xA9, // Usage(Vendor defined)
|
||||
0x75, 0x08, // Report Size
|
||||
0x95, 0x7F, // Report Count = 127 Btyes
|
||||
0x91, 0x02, // Output(Data, Variable, Absolute)
|
||||
0xC0, // End Collection
|
||||
#endif
|
||||
|
||||
};
|
||||
|
||||
/// Battery Service Attributes Indexes
|
||||
enum
|
||||
{
|
||||
BAS_IDX_SVC,
|
||||
|
||||
BAS_IDX_BATT_LVL_CHAR,
|
||||
BAS_IDX_BATT_LVL_VAL,
|
||||
BAS_IDX_BATT_LVL_NTF_CFG,
|
||||
BAS_IDX_BATT_LVL_PRES_FMT,
|
||||
|
||||
BAS_IDX_NB,
|
||||
};
|
||||
|
||||
#define HI_UINT16(a) (((a) >> 8) & 0xFF)
|
||||
#define LO_UINT16(a) ((a) & 0xFF)
|
||||
#define PROFILE_NUM 1
|
||||
#define PROFILE_APP_IDX 0
|
||||
|
||||
struct gatts_profile_inst {
|
||||
esp_gatts_cb_t gatts_cb;
|
||||
uint16_t gatts_if;
|
||||
uint16_t app_id;
|
||||
uint16_t conn_id;
|
||||
};
|
||||
|
||||
hidd_le_env_t hidd_le_env;
|
||||
|
||||
// HID report map length
|
||||
uint8_t hidReportMapLen = sizeof(hidReportMap);
|
||||
uint8_t hidProtocolMode = HID_PROTOCOL_MODE_REPORT;
|
||||
|
||||
// HID report mapping table
|
||||
//static hidRptMap_t hidRptMap[HID_NUM_REPORTS];
|
||||
|
||||
// HID Information characteristic value
|
||||
static const uint8_t hidInfo[HID_INFORMATION_LEN] = {
|
||||
LO_UINT16(0x0111), HI_UINT16(0x0111), // bcdHID (USB HID version)
|
||||
0x00, // bCountryCode
|
||||
HID_KBD_FLAGS // Flags
|
||||
};
|
||||
|
||||
|
||||
// HID External Report Reference Descriptor
|
||||
static uint16_t hidExtReportRefDesc = ESP_GATT_UUID_BATTERY_LEVEL;
|
||||
|
||||
// HID Report Reference characteristic descriptor, mouse input
|
||||
static uint8_t hidReportRefMouseIn[HID_REPORT_REF_LEN] =
|
||||
{ HID_RPT_ID_MOUSE_IN, HID_REPORT_TYPE_INPUT };
|
||||
|
||||
|
||||
// HID Report Reference characteristic descriptor, key input
|
||||
static uint8_t hidReportRefKeyIn[HID_REPORT_REF_LEN] =
|
||||
{ HID_RPT_ID_KEY_IN, HID_REPORT_TYPE_INPUT };
|
||||
|
||||
// HID Report Reference characteristic descriptor, LED output
|
||||
static uint8_t hidReportRefLedOut[HID_REPORT_REF_LEN] =
|
||||
{ HID_RPT_ID_LED_OUT, HID_REPORT_TYPE_OUTPUT };
|
||||
|
||||
#if (SUPPORT_REPORT_VENDOR == true)
|
||||
|
||||
static uint8_t hidReportRefVendorOut[HID_REPORT_REF_LEN] =
|
||||
{HID_RPT_ID_VENDOR_OUT, HID_REPORT_TYPE_OUTPUT};
|
||||
#endif
|
||||
|
||||
// HID Report Reference characteristic descriptor, Feature
|
||||
static uint8_t hidReportRefFeature[HID_REPORT_REF_LEN] =
|
||||
{ HID_RPT_ID_FEATURE, HID_REPORT_TYPE_FEATURE };
|
||||
|
||||
// HID Report Reference characteristic descriptor, consumer control input
|
||||
static uint8_t hidReportRefCCIn[HID_REPORT_REF_LEN] =
|
||||
{ HID_RPT_ID_CC_IN, HID_REPORT_TYPE_INPUT };
|
||||
|
||||
|
||||
/*
|
||||
* Heart Rate PROFILE ATTRIBUTES
|
||||
****************************************************************************************
|
||||
*/
|
||||
|
||||
/// hid Service uuid
|
||||
static uint16_t hid_le_svc = ATT_SVC_HID;
|
||||
uint16_t hid_count = 0;
|
||||
esp_gatts_incl_svc_desc_t incl_svc = {0};
|
||||
|
||||
#define CHAR_DECLARATION_SIZE (sizeof(uint8_t))
|
||||
///the uuid definition
|
||||
static const uint16_t primary_service_uuid = ESP_GATT_UUID_PRI_SERVICE;
|
||||
static const uint16_t include_service_uuid = ESP_GATT_UUID_INCLUDE_SERVICE;
|
||||
static const uint16_t character_declaration_uuid = ESP_GATT_UUID_CHAR_DECLARE;
|
||||
static const uint16_t character_client_config_uuid = ESP_GATT_UUID_CHAR_CLIENT_CONFIG;
|
||||
static const uint16_t hid_info_char_uuid = ESP_GATT_UUID_HID_INFORMATION;
|
||||
static const uint16_t hid_report_map_uuid = ESP_GATT_UUID_HID_REPORT_MAP;
|
||||
static const uint16_t hid_control_point_uuid = ESP_GATT_UUID_HID_CONTROL_POINT;
|
||||
static const uint16_t hid_report_uuid = ESP_GATT_UUID_HID_REPORT;
|
||||
static const uint16_t hid_proto_mode_uuid = ESP_GATT_UUID_HID_PROTO_MODE;
|
||||
static const uint16_t hid_kb_input_uuid = ESP_GATT_UUID_HID_BT_KB_INPUT;
|
||||
static const uint16_t hid_kb_output_uuid = ESP_GATT_UUID_HID_BT_KB_OUTPUT;
|
||||
static const uint16_t hid_mouse_input_uuid = ESP_GATT_UUID_HID_BT_MOUSE_INPUT;
|
||||
static const uint16_t hid_repot_map_ext_desc_uuid = ESP_GATT_UUID_EXT_RPT_REF_DESCR;
|
||||
static const uint16_t hid_report_ref_descr_uuid = ESP_GATT_UUID_RPT_REF_DESCR;
|
||||
///the propoty definition
|
||||
static const uint8_t char_prop_notify = ESP_GATT_CHAR_PROP_BIT_NOTIFY;
|
||||
static const uint8_t char_prop_read = ESP_GATT_CHAR_PROP_BIT_READ;
|
||||
static const uint8_t char_prop_write_nr = ESP_GATT_CHAR_PROP_BIT_WRITE_NR;
|
||||
static const uint8_t char_prop_read_write = ESP_GATT_CHAR_PROP_BIT_WRITE|ESP_GATT_CHAR_PROP_BIT_READ;
|
||||
static const uint8_t char_prop_read_notify = ESP_GATT_CHAR_PROP_BIT_READ|ESP_GATT_CHAR_PROP_BIT_NOTIFY;
|
||||
static const uint8_t char_prop_read_write_notify = ESP_GATT_CHAR_PROP_BIT_READ|ESP_GATT_CHAR_PROP_BIT_WRITE|ESP_GATT_CHAR_PROP_BIT_NOTIFY;
|
||||
|
||||
/// battary Service
|
||||
static const uint16_t battary_svc = ESP_GATT_UUID_BATTERY_SERVICE_SVC;
|
||||
|
||||
static const uint16_t bat_lev_uuid = ESP_GATT_UUID_BATTERY_LEVEL;
|
||||
static const uint8_t bat_lev_ccc[2] ={ 0x00, 0x00};
|
||||
static const uint16_t char_format_uuid = ESP_GATT_UUID_CHAR_PRESENT_FORMAT;
|
||||
|
||||
static uint8_t battary_lev = 50;
|
||||
/// Full HRS Database Description - Used to add attributes into the database
|
||||
static const esp_gatts_attr_db_t bas_att_db[BAS_IDX_NB] =
|
||||
{
|
||||
// Battary Service Declaration
|
||||
[BAS_IDX_SVC] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&primary_service_uuid, ESP_GATT_PERM_READ,
|
||||
sizeof(uint16_t), sizeof(battary_svc), (uint8_t *)&battary_svc}},
|
||||
|
||||
// Battary level Characteristic Declaration
|
||||
[BAS_IDX_BATT_LVL_CHAR] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_declaration_uuid, ESP_GATT_PERM_READ,
|
||||
CHAR_DECLARATION_SIZE,CHAR_DECLARATION_SIZE, (uint8_t *)&char_prop_read_notify}},
|
||||
|
||||
// Battary level Characteristic Value
|
||||
[BAS_IDX_BATT_LVL_VAL] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&bat_lev_uuid, ESP_GATT_PERM_READ,
|
||||
sizeof(uint8_t),sizeof(uint8_t), &battary_lev}},
|
||||
|
||||
// Battary level Characteristic - Client Characteristic Configuration Descriptor
|
||||
[BAS_IDX_BATT_LVL_NTF_CFG] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_client_config_uuid, ESP_GATT_PERM_READ|ESP_GATT_PERM_WRITE,
|
||||
sizeof(uint16_t),sizeof(bat_lev_ccc), (uint8_t *)bat_lev_ccc}},
|
||||
|
||||
// Battary level report Characteristic Declaration
|
||||
[BAS_IDX_BATT_LVL_PRES_FMT] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&char_format_uuid, ESP_GATT_PERM_READ,
|
||||
sizeof(struct prf_char_pres_fmt), 0, NULL}},
|
||||
};
|
||||
|
||||
|
||||
/// Full Hid device Database Description - Used to add attributes into the database
|
||||
static esp_gatts_attr_db_t hidd_le_gatt_db[HIDD_LE_IDX_NB] =
|
||||
{
|
||||
// HID Service Declaration
|
||||
[HIDD_LE_IDX_SVC] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&primary_service_uuid,
|
||||
ESP_GATT_PERM_READ_ENCRYPTED, sizeof(uint16_t), sizeof(hid_le_svc),
|
||||
(uint8_t *)&hid_le_svc}},
|
||||
|
||||
// HID Service Declaration
|
||||
[HIDD_LE_IDX_INCL_SVC] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&include_service_uuid,
|
||||
ESP_GATT_PERM_READ,
|
||||
sizeof(esp_gatts_incl_svc_desc_t), sizeof(esp_gatts_incl_svc_desc_t),
|
||||
(uint8_t *)&incl_svc}},
|
||||
|
||||
// HID Information Characteristic Declaration
|
||||
[HIDD_LE_IDX_HID_INFO_CHAR] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_declaration_uuid,
|
||||
ESP_GATT_PERM_READ,
|
||||
CHAR_DECLARATION_SIZE, CHAR_DECLARATION_SIZE,
|
||||
(uint8_t *)&char_prop_read}},
|
||||
// HID Information Characteristic Value
|
||||
[HIDD_LE_IDX_HID_INFO_VAL] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&hid_info_char_uuid,
|
||||
ESP_GATT_PERM_READ,
|
||||
sizeof(hids_hid_info_t), sizeof(hidInfo),
|
||||
(uint8_t *)&hidInfo}},
|
||||
|
||||
// HID Control Point Characteristic Declaration
|
||||
[HIDD_LE_IDX_HID_CTNL_PT_CHAR] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_declaration_uuid,
|
||||
ESP_GATT_PERM_READ,
|
||||
CHAR_DECLARATION_SIZE, CHAR_DECLARATION_SIZE,
|
||||
(uint8_t *)&char_prop_write_nr}},
|
||||
// HID Control Point Characteristic Value
|
||||
[HIDD_LE_IDX_HID_CTNL_PT_VAL] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&hid_control_point_uuid,
|
||||
ESP_GATT_PERM_WRITE,
|
||||
sizeof(uint8_t), 0,
|
||||
NULL}},
|
||||
|
||||
// Report Map Characteristic Declaration
|
||||
[HIDD_LE_IDX_REPORT_MAP_CHAR] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_declaration_uuid,
|
||||
ESP_GATT_PERM_READ,
|
||||
CHAR_DECLARATION_SIZE, CHAR_DECLARATION_SIZE,
|
||||
(uint8_t *)&char_prop_read}},
|
||||
// Report Map Characteristic Value
|
||||
[HIDD_LE_IDX_REPORT_MAP_VAL] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&hid_report_map_uuid,
|
||||
ESP_GATT_PERM_READ,
|
||||
HIDD_LE_REPORT_MAP_MAX_LEN, sizeof(hidReportMap),
|
||||
(uint8_t *)&hidReportMap}},
|
||||
|
||||
// Report Map Characteristic - External Report Reference Descriptor
|
||||
[HIDD_LE_IDX_REPORT_MAP_EXT_REP_REF] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&hid_repot_map_ext_desc_uuid,
|
||||
ESP_GATT_PERM_READ,
|
||||
sizeof(uint16_t), sizeof(uint16_t),
|
||||
(uint8_t *)&hidExtReportRefDesc}},
|
||||
|
||||
// Protocol Mode Characteristic Declaration
|
||||
[HIDD_LE_IDX_PROTO_MODE_CHAR] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_declaration_uuid,
|
||||
ESP_GATT_PERM_READ,
|
||||
CHAR_DECLARATION_SIZE, CHAR_DECLARATION_SIZE,
|
||||
(uint8_t *)&char_prop_read_write}},
|
||||
// Protocol Mode Characteristic Value
|
||||
[HIDD_LE_IDX_PROTO_MODE_VAL] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&hid_proto_mode_uuid,
|
||||
(ESP_GATT_PERM_READ|ESP_GATT_PERM_WRITE),
|
||||
sizeof(uint8_t), sizeof(hidProtocolMode),
|
||||
(uint8_t *)&hidProtocolMode}},
|
||||
|
||||
[HIDD_LE_IDX_REPORT_MOUSE_IN_CHAR] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_declaration_uuid,
|
||||
ESP_GATT_PERM_READ,
|
||||
CHAR_DECLARATION_SIZE, CHAR_DECLARATION_SIZE,
|
||||
(uint8_t *)&char_prop_read_notify}},
|
||||
|
||||
[HIDD_LE_IDX_REPORT_MOUSE_IN_VAL] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&hid_report_uuid,
|
||||
ESP_GATT_PERM_READ,
|
||||
HIDD_LE_REPORT_MAX_LEN, 0,
|
||||
NULL}},
|
||||
|
||||
[HIDD_LE_IDX_REPORT_MOUSE_IN_CCC] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_client_config_uuid,
|
||||
(ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE),
|
||||
sizeof(uint16_t), 0,
|
||||
NULL}},
|
||||
|
||||
[HIDD_LE_IDX_REPORT_MOUSE_REP_REF] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&hid_report_ref_descr_uuid,
|
||||
ESP_GATT_PERM_READ,
|
||||
sizeof(hidReportRefMouseIn), sizeof(hidReportRefMouseIn),
|
||||
hidReportRefMouseIn}},
|
||||
// Report Characteristic Declaration
|
||||
[HIDD_LE_IDX_REPORT_KEY_IN_CHAR] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_declaration_uuid,
|
||||
ESP_GATT_PERM_READ,
|
||||
CHAR_DECLARATION_SIZE, CHAR_DECLARATION_SIZE,
|
||||
(uint8_t *)&char_prop_read_notify}},
|
||||
// Report Characteristic Value
|
||||
[HIDD_LE_IDX_REPORT_KEY_IN_VAL] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&hid_report_uuid,
|
||||
ESP_GATT_PERM_READ,
|
||||
HIDD_LE_REPORT_MAX_LEN, 0,
|
||||
NULL}},
|
||||
// Report KEY INPUT Characteristic - Client Characteristic Configuration Descriptor
|
||||
[HIDD_LE_IDX_REPORT_KEY_IN_CCC] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_client_config_uuid,
|
||||
(ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE),
|
||||
sizeof(uint16_t), 0,
|
||||
NULL}},
|
||||
// Report Characteristic - Report Reference Descriptor
|
||||
[HIDD_LE_IDX_REPORT_KEY_IN_REP_REF] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&hid_report_ref_descr_uuid,
|
||||
ESP_GATT_PERM_READ,
|
||||
sizeof(hidReportRefKeyIn), sizeof(hidReportRefKeyIn),
|
||||
hidReportRefKeyIn}},
|
||||
|
||||
// Report Characteristic Declaration
|
||||
[HIDD_LE_IDX_REPORT_LED_OUT_CHAR] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_declaration_uuid,
|
||||
ESP_GATT_PERM_READ,
|
||||
CHAR_DECLARATION_SIZE, CHAR_DECLARATION_SIZE,
|
||||
(uint8_t *)&char_prop_read_write}},
|
||||
|
||||
[HIDD_LE_IDX_REPORT_LED_OUT_VAL] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&hid_report_uuid,
|
||||
ESP_GATT_PERM_READ|ESP_GATT_PERM_WRITE,
|
||||
HIDD_LE_REPORT_MAX_LEN, 0,
|
||||
NULL}},
|
||||
[HIDD_LE_IDX_REPORT_LED_OUT_REP_REF] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&hid_report_ref_descr_uuid,
|
||||
ESP_GATT_PERM_READ,
|
||||
sizeof(hidReportRefLedOut), sizeof(hidReportRefLedOut),
|
||||
hidReportRefLedOut}},
|
||||
#if (SUPPORT_REPORT_VENDOR == true)
|
||||
// Report Characteristic Declaration
|
||||
[HIDD_LE_IDX_REPORT_VENDOR_OUT_CHAR] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_declaration_uuid,
|
||||
ESP_GATT_PERM_READ,
|
||||
CHAR_DECLARATION_SIZE, CHAR_DECLARATION_SIZE,
|
||||
(uint8_t *)&char_prop_read_write_notify}},
|
||||
[HIDD_LE_IDX_REPORT_VENDOR_OUT_VAL] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&hid_report_uuid,
|
||||
ESP_GATT_PERM_READ|ESP_GATT_PERM_WRITE,
|
||||
HIDD_LE_REPORT_MAX_LEN, 0,
|
||||
NULL}},
|
||||
[HIDD_LE_IDX_REPORT_VENDOR_OUT_REP_REF] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&hid_report_ref_descr_uuid,
|
||||
ESP_GATT_PERM_READ,
|
||||
sizeof(hidReportRefVendorOut), sizeof(hidReportRefVendorOut),
|
||||
hidReportRefVendorOut}},
|
||||
#endif
|
||||
// Report Characteristic Declaration
|
||||
[HIDD_LE_IDX_REPORT_CC_IN_CHAR] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_declaration_uuid,
|
||||
ESP_GATT_PERM_READ,
|
||||
CHAR_DECLARATION_SIZE, CHAR_DECLARATION_SIZE,
|
||||
(uint8_t *)&char_prop_read_notify}},
|
||||
// Report Characteristic Value
|
||||
[HIDD_LE_IDX_REPORT_CC_IN_VAL] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&hid_report_uuid,
|
||||
ESP_GATT_PERM_READ,
|
||||
HIDD_LE_REPORT_MAX_LEN, 0,
|
||||
NULL}},
|
||||
// Report KEY INPUT Characteristic - Client Characteristic Configuration Descriptor
|
||||
[HIDD_LE_IDX_REPORT_CC_IN_CCC] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_client_config_uuid,
|
||||
(ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE_ENCRYPTED),
|
||||
sizeof(uint16_t), 0,
|
||||
NULL}},
|
||||
// Report Characteristic - Report Reference Descriptor
|
||||
[HIDD_LE_IDX_REPORT_CC_IN_REP_REF] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&hid_report_ref_descr_uuid,
|
||||
ESP_GATT_PERM_READ,
|
||||
sizeof(hidReportRefCCIn), sizeof(hidReportRefCCIn),
|
||||
hidReportRefCCIn}},
|
||||
|
||||
// Boot Keyboard Input Report Characteristic Declaration
|
||||
[HIDD_LE_IDX_BOOT_KB_IN_REPORT_CHAR] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_declaration_uuid,
|
||||
ESP_GATT_PERM_READ,
|
||||
CHAR_DECLARATION_SIZE, CHAR_DECLARATION_SIZE,
|
||||
(uint8_t *)&char_prop_read_notify}},
|
||||
// Boot Keyboard Input Report Characteristic Value
|
||||
[HIDD_LE_IDX_BOOT_KB_IN_REPORT_VAL] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&hid_kb_input_uuid,
|
||||
ESP_GATT_PERM_READ,
|
||||
HIDD_LE_BOOT_REPORT_MAX_LEN, 0,
|
||||
NULL}},
|
||||
// Boot Keyboard Input Report Characteristic - Client Characteristic Configuration Descriptor
|
||||
[HIDD_LE_IDX_BOOT_KB_IN_REPORT_NTF_CFG] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_client_config_uuid,
|
||||
(ESP_GATT_PERM_READ|ESP_GATT_PERM_WRITE),
|
||||
sizeof(uint16_t), 0,
|
||||
NULL}},
|
||||
|
||||
// Boot Keyboard Output Report Characteristic Declaration
|
||||
[HIDD_LE_IDX_BOOT_KB_OUT_REPORT_CHAR] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_declaration_uuid,
|
||||
ESP_GATT_PERM_READ,
|
||||
CHAR_DECLARATION_SIZE, CHAR_DECLARATION_SIZE,
|
||||
(uint8_t *)&char_prop_read_write}},
|
||||
// Boot Keyboard Output Report Characteristic Value
|
||||
[HIDD_LE_IDX_BOOT_KB_OUT_REPORT_VAL] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&hid_kb_output_uuid,
|
||||
(ESP_GATT_PERM_READ|ESP_GATT_PERM_WRITE),
|
||||
HIDD_LE_BOOT_REPORT_MAX_LEN, 0,
|
||||
NULL}},
|
||||
|
||||
// Boot Mouse Input Report Characteristic Declaration
|
||||
[HIDD_LE_IDX_BOOT_MOUSE_IN_REPORT_CHAR] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_declaration_uuid,
|
||||
ESP_GATT_PERM_READ,
|
||||
CHAR_DECLARATION_SIZE, CHAR_DECLARATION_SIZE,
|
||||
(uint8_t *)&char_prop_read_notify}},
|
||||
// Boot Mouse Input Report Characteristic Value
|
||||
[HIDD_LE_IDX_BOOT_MOUSE_IN_REPORT_VAL] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&hid_mouse_input_uuid,
|
||||
ESP_GATT_PERM_READ,
|
||||
HIDD_LE_BOOT_REPORT_MAX_LEN, 0,
|
||||
NULL}},
|
||||
// Boot Mouse Input Report Characteristic - Client Characteristic Configuration Descriptor
|
||||
[HIDD_LE_IDX_BOOT_MOUSE_IN_REPORT_NTF_CFG] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_client_config_uuid,
|
||||
(ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE),
|
||||
sizeof(uint16_t), 0,
|
||||
NULL}},
|
||||
|
||||
// Report Characteristic Declaration
|
||||
[HIDD_LE_IDX_REPORT_CHAR] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_declaration_uuid,
|
||||
ESP_GATT_PERM_READ,
|
||||
CHAR_DECLARATION_SIZE, CHAR_DECLARATION_SIZE,
|
||||
(uint8_t *)&char_prop_read_write}},
|
||||
// Report Characteristic Value
|
||||
[HIDD_LE_IDX_REPORT_VAL] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&hid_report_uuid,
|
||||
ESP_GATT_PERM_READ,
|
||||
HIDD_LE_REPORT_MAX_LEN, 0,
|
||||
NULL}},
|
||||
// Report Characteristic - Report Reference Descriptor
|
||||
[HIDD_LE_IDX_REPORT_REP_REF] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&hid_report_ref_descr_uuid,
|
||||
ESP_GATT_PERM_READ,
|
||||
sizeof(hidReportRefFeature), sizeof(hidReportRefFeature),
|
||||
hidReportRefFeature}},
|
||||
};
|
||||
|
||||
static void hid_add_id_tbl(void);
|
||||
|
||||
void esp_hidd_prf_cb_hdl(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if,
|
||||
esp_ble_gatts_cb_param_t *param)
|
||||
{
|
||||
switch(event) {
|
||||
case ESP_GATTS_REG_EVT: {
|
||||
esp_ble_gap_config_local_icon (ESP_BLE_APPEARANCE_GENERIC_HID);
|
||||
esp_hidd_cb_param_t hidd_param;
|
||||
hidd_param.init_finish.state = param->reg.status;
|
||||
if(param->reg.app_id == HIDD_APP_ID) {
|
||||
hidd_le_env.gatt_if = gatts_if;
|
||||
if(hidd_le_env.hidd_cb != NULL) {
|
||||
(hidd_le_env.hidd_cb)(ESP_HIDD_EVENT_REG_FINISH, &hidd_param);
|
||||
hidd_le_create_service(hidd_le_env.gatt_if);
|
||||
}
|
||||
}
|
||||
if(param->reg.app_id == BATTRAY_APP_ID) {
|
||||
hidd_param.init_finish.gatts_if = gatts_if;
|
||||
if(hidd_le_env.hidd_cb != NULL) {
|
||||
(hidd_le_env.hidd_cb)(ESP_BAT_EVENT_REG, &hidd_param);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case ESP_GATTS_CONF_EVT: {
|
||||
break;
|
||||
}
|
||||
case ESP_GATTS_CREATE_EVT:
|
||||
break;
|
||||
case ESP_GATTS_CONNECT_EVT: {
|
||||
esp_hidd_cb_param_t cb_param = {0};
|
||||
ESP_LOGI(HID_LE_PRF_TAG, "HID connection establish, conn_id = %x",param->connect.conn_id);
|
||||
memcpy(cb_param.connect.remote_bda, param->connect.remote_bda, sizeof(esp_bd_addr_t));
|
||||
cb_param.connect.conn_id = param->connect.conn_id;
|
||||
hidd_clcb_alloc(param->connect.conn_id, param->connect.remote_bda);
|
||||
esp_ble_set_encryption(param->connect.remote_bda, ESP_BLE_SEC_ENCRYPT_NO_MITM);
|
||||
if(hidd_le_env.hidd_cb != NULL) {
|
||||
(hidd_le_env.hidd_cb)(ESP_HIDD_EVENT_BLE_CONNECT, &cb_param);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ESP_GATTS_DISCONNECT_EVT: {
|
||||
if(hidd_le_env.hidd_cb != NULL) {
|
||||
(hidd_le_env.hidd_cb)(ESP_HIDD_EVENT_BLE_DISCONNECT, NULL);
|
||||
}
|
||||
hidd_clcb_dealloc(param->disconnect.conn_id);
|
||||
break;
|
||||
}
|
||||
case ESP_GATTS_CLOSE_EVT:
|
||||
break;
|
||||
case ESP_GATTS_WRITE_EVT: {
|
||||
#if (SUPPORT_REPORT_VENDOR == true)
|
||||
esp_hidd_cb_param_t cb_param = {0};
|
||||
if (param->write.handle == hidd_le_env.hidd_inst.att_tbl[HIDD_LE_IDX_REPORT_VENDOR_OUT_VAL] &&
|
||||
hidd_le_env.hidd_cb != NULL) {
|
||||
cb_param.vendor_write.conn_id = param->write.conn_id;
|
||||
cb_param.vendor_write.report_id = HID_RPT_ID_VENDOR_OUT;
|
||||
cb_param.vendor_write.length = param->write.len;
|
||||
cb_param.vendor_write.data = param->write.value;
|
||||
(hidd_le_env.hidd_cb)(ESP_HIDD_EVENT_BLE_VENDOR_REPORT_WRITE_EVT, &cb_param);
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
case ESP_GATTS_CREAT_ATTR_TAB_EVT: {
|
||||
if (param->add_attr_tab.num_handle == BAS_IDX_NB &&
|
||||
param->add_attr_tab.svc_uuid.uuid.uuid16 == ESP_GATT_UUID_BATTERY_SERVICE_SVC &&
|
||||
param->add_attr_tab.status == ESP_GATT_OK) {
|
||||
incl_svc.start_hdl = param->add_attr_tab.handles[BAS_IDX_SVC];
|
||||
incl_svc.end_hdl = incl_svc.start_hdl + BAS_IDX_NB -1;
|
||||
ESP_LOGI(HID_LE_PRF_TAG, "%s(), start added the hid service to the stack database. incl_handle = %d",
|
||||
__func__, incl_svc.start_hdl);
|
||||
esp_ble_gatts_create_attr_tab(hidd_le_gatt_db, gatts_if, HIDD_LE_IDX_NB, 0);
|
||||
}
|
||||
if (param->add_attr_tab.num_handle == HIDD_LE_IDX_NB &&
|
||||
param->add_attr_tab.status == ESP_GATT_OK) {
|
||||
memcpy(hidd_le_env.hidd_inst.att_tbl, param->add_attr_tab.handles,
|
||||
HIDD_LE_IDX_NB*sizeof(uint16_t));
|
||||
ESP_LOGI(HID_LE_PRF_TAG, "hid svc handle = %x",hidd_le_env.hidd_inst.att_tbl[HIDD_LE_IDX_SVC]);
|
||||
hid_add_id_tbl();
|
||||
esp_ble_gatts_start_service(hidd_le_env.hidd_inst.att_tbl[HIDD_LE_IDX_SVC]);
|
||||
} else {
|
||||
esp_ble_gatts_start_service(param->add_attr_tab.handles[0]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void hidd_le_create_service(esp_gatt_if_t gatts_if)
|
||||
{
|
||||
/* Here should added the battery service first, because the hid service should include the battery service.
|
||||
After finish to added the battery service then can added the hid service. */
|
||||
esp_ble_gatts_create_attr_tab(bas_att_db, gatts_if, BAS_IDX_NB, 0);
|
||||
|
||||
}
|
||||
|
||||
void hidd_le_init(void)
|
||||
{
|
||||
|
||||
// Reset the hid device target environment
|
||||
memset(&hidd_le_env, 0, sizeof(hidd_le_env_t));
|
||||
}
|
||||
|
||||
void hidd_clcb_alloc (uint16_t conn_id, esp_bd_addr_t bda)
|
||||
{
|
||||
uint8_t i_clcb = 0;
|
||||
hidd_clcb_t *p_clcb = NULL;
|
||||
|
||||
for (i_clcb = 0, p_clcb= hidd_le_env.hidd_clcb; i_clcb < HID_MAX_APPS; i_clcb++, p_clcb++) {
|
||||
if (!p_clcb->in_use) {
|
||||
p_clcb->in_use = true;
|
||||
p_clcb->conn_id = conn_id;
|
||||
p_clcb->connected = true;
|
||||
memcpy (p_clcb->remote_bda, bda, ESP_BD_ADDR_LEN);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
bool hidd_clcb_dealloc (uint16_t conn_id)
|
||||
{
|
||||
uint8_t i_clcb = 0;
|
||||
hidd_clcb_t *p_clcb = NULL;
|
||||
|
||||
for (i_clcb = 0, p_clcb= hidd_le_env.hidd_clcb; i_clcb < HID_MAX_APPS; i_clcb++, p_clcb++) {
|
||||
memset(p_clcb, 0, sizeof(hidd_clcb_t));
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static struct gatts_profile_inst heart_rate_profile_tab[PROFILE_NUM] = {
|
||||
[PROFILE_APP_IDX] = {
|
||||
.gatts_cb = esp_hidd_prf_cb_hdl,
|
||||
.gatts_if = ESP_GATT_IF_NONE, /* Not get the gatt_if, so initial is ESP_GATT_IF_NONE */
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
static void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if,
|
||||
esp_ble_gatts_cb_param_t *param)
|
||||
{
|
||||
/* If event is register event, store the gatts_if for each profile */
|
||||
if (event == ESP_GATTS_REG_EVT) {
|
||||
if (param->reg.status == ESP_GATT_OK) {
|
||||
heart_rate_profile_tab[PROFILE_APP_IDX].gatts_if = gatts_if;
|
||||
} else {
|
||||
ESP_LOGI(HID_LE_PRF_TAG, "Reg app failed, app_id %04x, status %d\n",
|
||||
param->reg.app_id,
|
||||
param->reg.status);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
do {
|
||||
int idx;
|
||||
for (idx = 0; idx < PROFILE_NUM; idx++) {
|
||||
if (gatts_if == ESP_GATT_IF_NONE || /* ESP_GATT_IF_NONE, not specify a certain gatt_if, need to call every profile cb function */
|
||||
gatts_if == heart_rate_profile_tab[idx].gatts_if) {
|
||||
if (heart_rate_profile_tab[idx].gatts_cb) {
|
||||
heart_rate_profile_tab[idx].gatts_cb(event, gatts_if, param);
|
||||
}
|
||||
}
|
||||
}
|
||||
} while (0);
|
||||
}
|
||||
|
||||
|
||||
esp_err_t hidd_register_cb(void)
|
||||
{
|
||||
esp_err_t status;
|
||||
status = esp_ble_gatts_register_callback(gatts_event_handler);
|
||||
return status;
|
||||
}
|
||||
|
||||
void hidd_set_attr_value(uint16_t handle, uint16_t val_len, const uint8_t *value)
|
||||
{
|
||||
hidd_inst_t *hidd_inst = &hidd_le_env.hidd_inst;
|
||||
if(hidd_inst->att_tbl[HIDD_LE_IDX_HID_INFO_VAL] <= handle &&
|
||||
hidd_inst->att_tbl[HIDD_LE_IDX_REPORT_REP_REF] >= handle) {
|
||||
esp_ble_gatts_set_attr_value(handle, val_len, value);
|
||||
} else {
|
||||
ESP_LOGE(HID_LE_PRF_TAG, "%s error:Invalid handle value.",__func__);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
void hidd_get_attr_value(uint16_t handle, uint16_t *length, uint8_t **value)
|
||||
{
|
||||
hidd_inst_t *hidd_inst = &hidd_le_env.hidd_inst;
|
||||
if(hidd_inst->att_tbl[HIDD_LE_IDX_HID_INFO_VAL] <= handle &&
|
||||
hidd_inst->att_tbl[HIDD_LE_IDX_REPORT_REP_REF] >= handle){
|
||||
esp_ble_gatts_get_attr_value(handle, length, (const uint8_t **)value);
|
||||
} else {
|
||||
ESP_LOGE(HID_LE_PRF_TAG, "%s error:Invalid handle value.", __func__);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
static void hid_add_id_tbl(void)
|
||||
{
|
||||
// Mouse input report
|
||||
hid_rpt_map[0].id = hidReportRefMouseIn[0];
|
||||
hid_rpt_map[0].type = hidReportRefMouseIn[1];
|
||||
hid_rpt_map[0].handle = hidd_le_env.hidd_inst.att_tbl[HIDD_LE_IDX_REPORT_MOUSE_IN_VAL];
|
||||
hid_rpt_map[0].cccdHandle = hidd_le_env.hidd_inst.att_tbl[HIDD_LE_IDX_REPORT_MOUSE_IN_VAL];
|
||||
hid_rpt_map[0].mode = HID_PROTOCOL_MODE_REPORT;
|
||||
|
||||
// Key input report
|
||||
hid_rpt_map[1].id = hidReportRefKeyIn[0];
|
||||
hid_rpt_map[1].type = hidReportRefKeyIn[1];
|
||||
hid_rpt_map[1].handle = hidd_le_env.hidd_inst.att_tbl[HIDD_LE_IDX_REPORT_KEY_IN_VAL];
|
||||
hid_rpt_map[1].cccdHandle = hidd_le_env.hidd_inst.att_tbl[HIDD_LE_IDX_REPORT_KEY_IN_CCC];
|
||||
hid_rpt_map[1].mode = HID_PROTOCOL_MODE_REPORT;
|
||||
|
||||
// Consumer Control input report
|
||||
hid_rpt_map[2].id = hidReportRefCCIn[0];
|
||||
hid_rpt_map[2].type = hidReportRefCCIn[1];
|
||||
hid_rpt_map[2].handle = hidd_le_env.hidd_inst.att_tbl[HIDD_LE_IDX_REPORT_CC_IN_VAL];
|
||||
hid_rpt_map[2].cccdHandle = hidd_le_env.hidd_inst.att_tbl[HIDD_LE_IDX_REPORT_CC_IN_CCC];
|
||||
hid_rpt_map[2].mode = HID_PROTOCOL_MODE_REPORT;
|
||||
|
||||
// LED output report
|
||||
hid_rpt_map[3].id = hidReportRefLedOut[0];
|
||||
hid_rpt_map[3].type = hidReportRefLedOut[1];
|
||||
hid_rpt_map[3].handle = hidd_le_env.hidd_inst.att_tbl[HIDD_LE_IDX_REPORT_LED_OUT_VAL];
|
||||
hid_rpt_map[3].cccdHandle = 0;
|
||||
hid_rpt_map[3].mode = HID_PROTOCOL_MODE_REPORT;
|
||||
|
||||
// Boot keyboard input report
|
||||
// Use same ID and type as key input report
|
||||
hid_rpt_map[4].id = hidReportRefKeyIn[0];
|
||||
hid_rpt_map[4].type = hidReportRefKeyIn[1];
|
||||
hid_rpt_map[4].handle = hidd_le_env.hidd_inst.att_tbl[HIDD_LE_IDX_BOOT_KB_IN_REPORT_VAL];
|
||||
hid_rpt_map[4].cccdHandle = 0;
|
||||
hid_rpt_map[4].mode = HID_PROTOCOL_MODE_BOOT;
|
||||
|
||||
// Boot keyboard output report
|
||||
// Use same ID and type as LED output report
|
||||
hid_rpt_map[5].id = hidReportRefLedOut[0];
|
||||
hid_rpt_map[5].type = hidReportRefLedOut[1];
|
||||
hid_rpt_map[5].handle = hidd_le_env.hidd_inst.att_tbl[HIDD_LE_IDX_BOOT_KB_OUT_REPORT_VAL];
|
||||
hid_rpt_map[5].cccdHandle = 0;
|
||||
hid_rpt_map[5].mode = HID_PROTOCOL_MODE_BOOT;
|
||||
|
||||
// Boot mouse input report
|
||||
// Use same ID and type as mouse input report
|
||||
hid_rpt_map[6].id = hidReportRefMouseIn[0];
|
||||
hid_rpt_map[6].type = hidReportRefMouseIn[1];
|
||||
hid_rpt_map[6].handle = hidd_le_env.hidd_inst.att_tbl[HIDD_LE_IDX_BOOT_MOUSE_IN_REPORT_VAL];
|
||||
hid_rpt_map[6].cccdHandle = 0;
|
||||
hid_rpt_map[6].mode = HID_PROTOCOL_MODE_BOOT;
|
||||
|
||||
// Feature report
|
||||
hid_rpt_map[7].id = hidReportRefFeature[0];
|
||||
hid_rpt_map[7].type = hidReportRefFeature[1];
|
||||
hid_rpt_map[7].handle = hidd_le_env.hidd_inst.att_tbl[HIDD_LE_IDX_REPORT_VAL];
|
||||
hid_rpt_map[7].cccdHandle = 0;
|
||||
hid_rpt_map[7].mode = HID_PROTOCOL_MODE_REPORT;
|
||||
|
||||
|
||||
// Setup report ID map
|
||||
hid_dev_register_reports(HID_NUM_REPORTS, hid_rpt_map);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,344 @@
|
||||
// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
|
||||
|
||||
#ifndef __HID_DEVICE_LE_PRF__
|
||||
#define __HID_DEVICE_LE_PRF__
|
||||
#include <stdbool.h>
|
||||
#include "esp_gatts_api.h"
|
||||
#include "esp_gatt_defs.h"
|
||||
#include "esp_hidd_prf_api.h"
|
||||
#include "esp_gap_ble_api.h"
|
||||
#include "hid_dev.h"
|
||||
|
||||
#define SUPPORT_REPORT_VENDOR false
|
||||
//HID BLE profile log tag
|
||||
#define HID_LE_PRF_TAG "HID_LE_PRF"
|
||||
|
||||
/// Maximal number of HIDS that can be added in the DB
|
||||
#ifndef USE_ONE_HIDS_INSTANCE
|
||||
#define HIDD_LE_NB_HIDS_INST_MAX (2)
|
||||
#else
|
||||
#define HIDD_LE_NB_HIDS_INST_MAX (1)
|
||||
#endif
|
||||
|
||||
#define HIDD_GREAT_VER 0x01 //Version + Subversion
|
||||
#define HIDD_SUB_VER 0x00 //Version + Subversion
|
||||
#define HIDD_VERSION ((HIDD_GREAT_VER<<8)|HIDD_SUB_VER) //Version + Subversion
|
||||
|
||||
#define HID_MAX_APPS 1
|
||||
|
||||
// Number of HID reports defined in the service
|
||||
#define HID_NUM_REPORTS 9
|
||||
|
||||
// HID Report IDs for the service
|
||||
#define HID_RPT_ID_MOUSE_IN 1 // Mouse input report ID
|
||||
#define HID_RPT_ID_KEY_IN 2 // Keyboard input report ID
|
||||
#define HID_RPT_ID_CC_IN 3 //Consumer Control input report ID
|
||||
#define HID_RPT_ID_VENDOR_OUT 4 // Vendor output report ID
|
||||
#define HID_RPT_ID_LED_OUT 0 // LED output report ID
|
||||
#define HID_RPT_ID_FEATURE 0 // Feature report ID
|
||||
|
||||
#define HIDD_APP_ID 0x1812//ATT_SVC_HID
|
||||
|
||||
#define BATTRAY_APP_ID 0x180f
|
||||
|
||||
|
||||
#define ATT_SVC_HID 0x1812
|
||||
|
||||
/// Maximal number of Report Char. that can be added in the DB for one HIDS - Up to 11
|
||||
#define HIDD_LE_NB_REPORT_INST_MAX (5)
|
||||
|
||||
/// Maximal length of Report Char. Value
|
||||
#define HIDD_LE_REPORT_MAX_LEN (255)
|
||||
/// Maximal length of Report Map Char. Value
|
||||
#define HIDD_LE_REPORT_MAP_MAX_LEN (512)
|
||||
|
||||
/// Length of Boot Report Char. Value Maximal Length
|
||||
#define HIDD_LE_BOOT_REPORT_MAX_LEN (8)
|
||||
|
||||
/// Boot KB Input Report Notification Configuration Bit Mask
|
||||
#define HIDD_LE_BOOT_KB_IN_NTF_CFG_MASK (0x40)
|
||||
/// Boot KB Input Report Notification Configuration Bit Mask
|
||||
#define HIDD_LE_BOOT_MOUSE_IN_NTF_CFG_MASK (0x80)
|
||||
/// Boot Report Notification Configuration Bit Mask
|
||||
#define HIDD_LE_REPORT_NTF_CFG_MASK (0x20)
|
||||
|
||||
|
||||
/* HID information flags */
|
||||
#define HID_FLAGS_REMOTE_WAKE 0x01 // RemoteWake
|
||||
#define HID_FLAGS_NORMALLY_CONNECTABLE 0x02 // NormallyConnectable
|
||||
|
||||
/* Control point commands */
|
||||
#define HID_CMD_SUSPEND 0x00 // Suspend
|
||||
#define HID_CMD_EXIT_SUSPEND 0x01 // Exit Suspend
|
||||
|
||||
/* HID protocol mode values */
|
||||
#define HID_PROTOCOL_MODE_BOOT 0x00 // Boot Protocol Mode
|
||||
#define HID_PROTOCOL_MODE_REPORT 0x01 // Report Protocol Mode
|
||||
|
||||
/* Attribute value lengths */
|
||||
#define HID_PROTOCOL_MODE_LEN 1 // HID Protocol Mode
|
||||
#define HID_INFORMATION_LEN 4 // HID Information
|
||||
#define HID_REPORT_REF_LEN 2 // HID Report Reference Descriptor
|
||||
#define HID_EXT_REPORT_REF_LEN 2 // External Report Reference Descriptor
|
||||
|
||||
// HID feature flags
|
||||
#define HID_KBD_FLAGS HID_FLAGS_REMOTE_WAKE
|
||||
|
||||
/* HID Report type */
|
||||
#define HID_REPORT_TYPE_INPUT 1
|
||||
#define HID_REPORT_TYPE_OUTPUT 2
|
||||
#define HID_REPORT_TYPE_FEATURE 3
|
||||
|
||||
|
||||
/// HID Service Attributes Indexes
|
||||
enum {
|
||||
HIDD_LE_IDX_SVC,
|
||||
|
||||
// Included Service
|
||||
HIDD_LE_IDX_INCL_SVC,
|
||||
|
||||
// HID Information
|
||||
HIDD_LE_IDX_HID_INFO_CHAR,
|
||||
HIDD_LE_IDX_HID_INFO_VAL,
|
||||
|
||||
// HID Control Point
|
||||
HIDD_LE_IDX_HID_CTNL_PT_CHAR,
|
||||
HIDD_LE_IDX_HID_CTNL_PT_VAL,
|
||||
|
||||
// Report Map
|
||||
HIDD_LE_IDX_REPORT_MAP_CHAR,
|
||||
HIDD_LE_IDX_REPORT_MAP_VAL,
|
||||
HIDD_LE_IDX_REPORT_MAP_EXT_REP_REF,
|
||||
|
||||
// Protocol Mode
|
||||
HIDD_LE_IDX_PROTO_MODE_CHAR,
|
||||
HIDD_LE_IDX_PROTO_MODE_VAL,
|
||||
|
||||
// Report mouse input
|
||||
HIDD_LE_IDX_REPORT_MOUSE_IN_CHAR,
|
||||
HIDD_LE_IDX_REPORT_MOUSE_IN_VAL,
|
||||
HIDD_LE_IDX_REPORT_MOUSE_IN_CCC,
|
||||
HIDD_LE_IDX_REPORT_MOUSE_REP_REF,
|
||||
//Report Key input
|
||||
HIDD_LE_IDX_REPORT_KEY_IN_CHAR,
|
||||
HIDD_LE_IDX_REPORT_KEY_IN_VAL,
|
||||
HIDD_LE_IDX_REPORT_KEY_IN_CCC,
|
||||
HIDD_LE_IDX_REPORT_KEY_IN_REP_REF,
|
||||
///Report Led output
|
||||
HIDD_LE_IDX_REPORT_LED_OUT_CHAR,
|
||||
HIDD_LE_IDX_REPORT_LED_OUT_VAL,
|
||||
HIDD_LE_IDX_REPORT_LED_OUT_REP_REF,
|
||||
|
||||
#if (SUPPORT_REPORT_VENDOR == true)
|
||||
/// Report Vendor
|
||||
HIDD_LE_IDX_REPORT_VENDOR_OUT_CHAR,
|
||||
HIDD_LE_IDX_REPORT_VENDOR_OUT_VAL,
|
||||
HIDD_LE_IDX_REPORT_VENDOR_OUT_REP_REF,
|
||||
#endif
|
||||
HIDD_LE_IDX_REPORT_CC_IN_CHAR,
|
||||
HIDD_LE_IDX_REPORT_CC_IN_VAL,
|
||||
HIDD_LE_IDX_REPORT_CC_IN_CCC,
|
||||
HIDD_LE_IDX_REPORT_CC_IN_REP_REF,
|
||||
|
||||
// Boot Keyboard Input Report
|
||||
HIDD_LE_IDX_BOOT_KB_IN_REPORT_CHAR,
|
||||
HIDD_LE_IDX_BOOT_KB_IN_REPORT_VAL,
|
||||
HIDD_LE_IDX_BOOT_KB_IN_REPORT_NTF_CFG,
|
||||
|
||||
// Boot Keyboard Output Report
|
||||
HIDD_LE_IDX_BOOT_KB_OUT_REPORT_CHAR,
|
||||
HIDD_LE_IDX_BOOT_KB_OUT_REPORT_VAL,
|
||||
|
||||
// Boot Mouse Input Report
|
||||
HIDD_LE_IDX_BOOT_MOUSE_IN_REPORT_CHAR,
|
||||
HIDD_LE_IDX_BOOT_MOUSE_IN_REPORT_VAL,
|
||||
HIDD_LE_IDX_BOOT_MOUSE_IN_REPORT_NTF_CFG,
|
||||
|
||||
// Report
|
||||
HIDD_LE_IDX_REPORT_CHAR,
|
||||
HIDD_LE_IDX_REPORT_VAL,
|
||||
HIDD_LE_IDX_REPORT_REP_REF,
|
||||
//HIDD_LE_IDX_REPORT_NTF_CFG,
|
||||
|
||||
HIDD_LE_IDX_NB,
|
||||
};
|
||||
|
||||
|
||||
/// Attribute Table Indexes
|
||||
enum {
|
||||
HIDD_LE_INFO_CHAR,
|
||||
HIDD_LE_CTNL_PT_CHAR,
|
||||
HIDD_LE_REPORT_MAP_CHAR,
|
||||
HIDD_LE_REPORT_CHAR,
|
||||
HIDD_LE_PROTO_MODE_CHAR,
|
||||
HIDD_LE_BOOT_KB_IN_REPORT_CHAR,
|
||||
HIDD_LE_BOOT_KB_OUT_REPORT_CHAR,
|
||||
HIDD_LE_BOOT_MOUSE_IN_REPORT_CHAR,
|
||||
HIDD_LE_CHAR_MAX //= HIDD_LE_REPORT_CHAR + HIDD_LE_NB_REPORT_INST_MAX,
|
||||
};
|
||||
|
||||
///att read event table Indexs
|
||||
enum {
|
||||
HIDD_LE_READ_INFO_EVT,
|
||||
HIDD_LE_READ_CTNL_PT_EVT,
|
||||
HIDD_LE_READ_REPORT_MAP_EVT,
|
||||
HIDD_LE_READ_REPORT_EVT,
|
||||
HIDD_LE_READ_PROTO_MODE_EVT,
|
||||
HIDD_LE_BOOT_KB_IN_REPORT_EVT,
|
||||
HIDD_LE_BOOT_KB_OUT_REPORT_EVT,
|
||||
HIDD_LE_BOOT_MOUSE_IN_REPORT_EVT,
|
||||
|
||||
HID_LE_EVT_MAX
|
||||
};
|
||||
|
||||
/// Client Characteristic Configuration Codes
|
||||
enum {
|
||||
HIDD_LE_DESC_MASK = 0x10,
|
||||
|
||||
HIDD_LE_BOOT_KB_IN_REPORT_CFG = HIDD_LE_BOOT_KB_IN_REPORT_CHAR | HIDD_LE_DESC_MASK,
|
||||
HIDD_LE_BOOT_MOUSE_IN_REPORT_CFG = HIDD_LE_BOOT_MOUSE_IN_REPORT_CHAR | HIDD_LE_DESC_MASK,
|
||||
HIDD_LE_REPORT_CFG = HIDD_LE_REPORT_CHAR | HIDD_LE_DESC_MASK,
|
||||
};
|
||||
|
||||
/// Features Flag Values
|
||||
enum {
|
||||
HIDD_LE_CFG_KEYBOARD = 0x01,
|
||||
HIDD_LE_CFG_MOUSE = 0x02,
|
||||
HIDD_LE_CFG_PROTO_MODE = 0x04,
|
||||
HIDD_LE_CFG_MAP_EXT_REF = 0x08,
|
||||
HIDD_LE_CFG_BOOT_KB_WR = 0x10,
|
||||
HIDD_LE_CFG_BOOT_MOUSE_WR = 0x20,
|
||||
};
|
||||
|
||||
/// Report Char. Configuration Flag Values
|
||||
enum {
|
||||
HIDD_LE_CFG_REPORT_IN = 0x01,
|
||||
HIDD_LE_CFG_REPORT_OUT = 0x02,
|
||||
//HOGPD_CFG_REPORT_FEAT can be used as a mask to check Report type
|
||||
HIDD_LE_CFG_REPORT_FEAT = 0x03,
|
||||
HIDD_LE_CFG_REPORT_WR = 0x10,
|
||||
};
|
||||
|
||||
/// Pointer to the connection clean-up function
|
||||
#define HIDD_LE_CLEANUP_FNCT (NULL)
|
||||
|
||||
/*
|
||||
* TYPE DEFINITIONS
|
||||
****************************************************************************************
|
||||
*/
|
||||
|
||||
/// HIDD Features structure
|
||||
typedef struct {
|
||||
/// Service Features
|
||||
uint8_t svc_features;
|
||||
/// Number of Report Char. instances to add in the database
|
||||
uint8_t report_nb;
|
||||
/// Report Char. Configuration
|
||||
uint8_t report_char_cfg[HIDD_LE_NB_REPORT_INST_MAX];
|
||||
} hidd_feature_t;
|
||||
|
||||
|
||||
typedef struct {
|
||||
bool in_use;
|
||||
bool congest;
|
||||
uint16_t conn_id;
|
||||
bool connected;
|
||||
esp_bd_addr_t remote_bda;
|
||||
uint32_t trans_id;
|
||||
uint8_t cur_srvc_id;
|
||||
|
||||
} hidd_clcb_t;
|
||||
|
||||
// HID report mapping table
|
||||
typedef struct {
|
||||
uint16_t handle; // Handle of report characteristic
|
||||
uint16_t cccdHandle; // Handle of CCCD for report characteristic
|
||||
uint8_t id; // Report ID
|
||||
uint8_t type; // Report type
|
||||
uint8_t mode; // Protocol mode (report or boot)
|
||||
} hidRptMap_t;
|
||||
|
||||
|
||||
typedef struct {
|
||||
/// hidd profile id
|
||||
uint8_t app_id;
|
||||
/// Notified handle
|
||||
uint16_t ntf_handle;
|
||||
///Attribute handle Table
|
||||
uint16_t att_tbl[HIDD_LE_IDX_NB];
|
||||
/// Supported Features
|
||||
hidd_feature_t hidd_feature[HIDD_LE_NB_HIDS_INST_MAX];
|
||||
/// Current Protocol Mode
|
||||
uint8_t proto_mode[HIDD_LE_NB_HIDS_INST_MAX];
|
||||
/// Number of HIDS added in the database
|
||||
uint8_t hids_nb;
|
||||
uint8_t pending_evt;
|
||||
uint16_t pending_hal;
|
||||
} hidd_inst_t;
|
||||
|
||||
/// Report Reference structure
|
||||
typedef struct
|
||||
{
|
||||
///Report ID
|
||||
uint8_t report_id;
|
||||
///Report Type
|
||||
uint8_t report_type;
|
||||
}hids_report_ref_t;
|
||||
|
||||
/// HID Information structure
|
||||
typedef struct
|
||||
{
|
||||
/// bcdHID
|
||||
uint16_t bcdHID;
|
||||
/// bCountryCode
|
||||
uint8_t bCountryCode;
|
||||
/// Flags
|
||||
uint8_t flags;
|
||||
}hids_hid_info_t;
|
||||
|
||||
|
||||
/* service engine control block */
|
||||
typedef struct {
|
||||
hidd_clcb_t hidd_clcb[HID_MAX_APPS]; /* connection link*/
|
||||
esp_gatt_if_t gatt_if;
|
||||
bool enabled;
|
||||
bool is_take;
|
||||
bool is_primery;
|
||||
hidd_inst_t hidd_inst;
|
||||
esp_hidd_event_cb_t hidd_cb;
|
||||
uint8_t inst_id;
|
||||
} hidd_le_env_t;
|
||||
|
||||
extern hidd_le_env_t hidd_le_env;
|
||||
extern uint8_t hidProtocolMode;
|
||||
|
||||
|
||||
void hidd_clcb_alloc (uint16_t conn_id, esp_bd_addr_t bda);
|
||||
|
||||
bool hidd_clcb_dealloc (uint16_t conn_id);
|
||||
|
||||
void hidd_le_create_service(esp_gatt_if_t gatts_if);
|
||||
|
||||
void hidd_set_attr_value(uint16_t handle, uint16_t val_len, const uint8_t *value);
|
||||
|
||||
void hidd_get_attr_value(uint16_t handle, uint16_t *length, uint8_t **value);
|
||||
|
||||
esp_err_t hidd_register_cb(void);
|
||||
|
||||
|
||||
#endif ///__HID_DEVICE_LE_PRF__
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
# Override some defaults so BT stack is enabled
|
||||
# by default in this example
|
||||
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
|
||||
@@ -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_ibeacon_demo)
|
||||
@@ -0,0 +1,10 @@
|
||||
#
|
||||
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
|
||||
# project subdirectory.
|
||||
#
|
||||
|
||||
PROJECT_NAME := ble_ibeacon_demo
|
||||
|
||||
COMPONENT_ADD_INCLUDEDIRS := components/include
|
||||
|
||||
include $(IDF_PATH)/make/project.mk
|
||||
@@ -0,0 +1,116 @@
|
||||
| Supported Targets | ESP32 |
|
||||
| ----------------- | ----- |
|
||||
|
||||
# ESP-IDF iBeacon demo
|
||||
|
||||
From welcoming people as they arrive at a sporting event to providing information about a nearby museum exhibit, iBeacon opens a new world of possibilities for location awareness, and countless opportunities for interactivity between iOS devices and iBeacon hardware.
|
||||
|
||||
## Using Example
|
||||
|
||||
iBeacon is a trademark of Apple Inc.
|
||||
|
||||
Before building devices which use iBeacon technology, visit https://developer.apple.com/ibeacon/ to obtain a license.
|
||||
|
||||
### iBeacon Mode
|
||||
|
||||
This example demonstrates iBeacon-compatible BLE advertising, and scanning of iBeacons:
|
||||
|
||||
- **IBEACON_SENDER**: demo to send iBeacon-compatible advertising data.
|
||||
|
||||
- **IBEACON_RECEIVER**: demo to receive and resolve iBeacon advertising data.
|
||||
|
||||
Which demo will be run depends on the menuconfig, developers can set it in `iBeacon Example Configuration`.
|
||||
|
||||
The default mode is iBeacon Sender.
|
||||
|
||||
### Menuconfig
|
||||
Before compiling the demo,developers also need to configure the project:
|
||||
|
||||
```c
|
||||
idf.py menuconfig
|
||||
```
|
||||
And then enter `Component config->Bluetooth->Bluedroid Enable`
|
||||
|
||||
Because the number of peripherals may be very large, developers can enable the **BLE Scan Duplicate Options**, the maximum number of devices in scan duplicate filter depends on the free heap size, when the cache is full, it is cleared.
|
||||
|
||||
### Event Processing
|
||||
In the iBeacon receiver demo, the scan result will be posted to `ESP_GAP_SEARCH_INQ_RES_EVT` event:
|
||||
|
||||
```c
|
||||
switch (scan_result->scan_rst.search_evt) {
|
||||
case ESP_GAP_SEARCH_INQ_RES_EVT:
|
||||
/* Search for BLE iBeacon Packet */
|
||||
......
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
```
|
||||
### Build and Flash
|
||||
|
||||
Build each project and flash it to the board, then run monitor tool to view serial output:
|
||||
|
||||
```
|
||||
idp.py -p PORT flash monitor
|
||||
```
|
||||
|
||||
(To exit the serial monitor, type ``Ctrl-]``.)
|
||||
|
||||
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
|
||||
|
||||
## Example Output
|
||||
|
||||
The iBeacon sender will broadcast iBeacon packet after initializing the Bluetooth protocol stack, and the iBeacon receiver will scan the iBeacon packet.
|
||||
|
||||
### iBeacon Sender
|
||||
|
||||
```
|
||||
I (384) boot: Loaded app from partition at offset 0x10000
|
||||
I (384) boot: Disabling RNG early entropy source...
|
||||
I (386) cpu_start: Pro cpu up.
|
||||
I (389) cpu_start: Starting app cpu, entry point is 0x40081010
|
||||
I (0) cpu_start: App cpu up.
|
||||
I (400) heap_init: Initializing. RAM available for dynamic allocation:
|
||||
I (406) heap_init: At 3FFAFF10 len 000000F0 (0 KiB): DRAM
|
||||
I (413) heap_init: At 3FFCCCA8 len 00013358 (76 KiB): DRAM
|
||||
I (419) heap_init: At 3FFE0440 len 00003BC0 (14 KiB): D/IRAM
|
||||
I (425) heap_init: At 3FFE4350 len 0001BCB0 (111 KiB): D/IRAM
|
||||
I (431) heap_init: At 40090E58 len 0000F1A8 (60 KiB): IRAM
|
||||
I (438) cpu_start: Pro cpu start user code
|
||||
I (120) cpu_start: Starting scheduler on PRO CPU
|
||||
I (0) cpu_start: Starting scheduler on APP CPU
|
||||
I (244) BTDM_INIT: BT controller compile version [44d04c1]
|
||||
|
||||
I (244) system_api: Base MAC address is not set, read default base MAC address from BLK0 of EFUSE
|
||||
I (624) phy: phy_version: 3910, c0c45a3, May 21 2018, 18:07:06, 0, 0
|
||||
I (654) IBEACON_DEMO: register callback
|
||||
```
|
||||
|
||||
### iBeacon Receiver
|
||||
|
||||
```
|
||||
I (384) boot: Loaded app from partition at offset 0x10000
|
||||
I (384) boot: Disabling RNG early entropy source...
|
||||
I (385) cpu_start: Pro cpu up.\0x1b[0m
|
||||
I (389) cpu_start: Starting app cpu, entry point is 0x40081010
|
||||
I (0) cpu_start: App cpu up.
|
||||
I (400) heap_init: Initializing. RAM available for dynamic allocation:
|
||||
I (406) heap_init: At 3FFAFF10 len 000000F0 (0 KiB): DRAM
|
||||
I (412) heap_init: At 3FFCCC88 len 00013378 (76 KiB): DRAM
|
||||
I (418) heap_init: At 3FFE0440 len 00003BC0 (14 KiB): D/IRAM
|
||||
I (425) heap_init: At 3FFE4350 len 0001BCB0 (111 KiB): D/IRAM
|
||||
I (431) heap_init: At 40090E58 len 0000F1A8 (60 KiB): IRAM
|
||||
I (437) cpu_start: Pro cpu start user code\0x1b[0m
|
||||
I (120) cpu_start: Starting scheduler on PRO CPU.
|
||||
I (0) cpu_start: Starting scheduler on APP CPU.
|
||||
I (243) BTDM_INIT: BT controller compile version [44d04c1]
|
||||
|
||||
I (243) system_api: Base MAC address is not set, read default base MAC address from BLK0 of EFUSE
|
||||
I (633) phy: phy_version: 3910, c0c45a3, May 21 2018, 18:07:06, 0, 0
|
||||
I (663) IBEACON_DEMO: register callback
|
||||
I (329203) IBEACON_DEMO: ----------iBeacon Found----------
|
||||
I (329203) IBEACON_DEMO: Device address:: 30 ae a4 00 42 82
|
||||
I (329203) IBEACON_DEMO: Proximity UUID:: fd a5 06 93 a4 e2 4f b1 af cf c6 eb 07 64 78 25
|
||||
```
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
idf_component_register(SRCS "esp_ibeacon_api.c"
|
||||
"ibeacon_demo.c"
|
||||
INCLUDE_DIRS ".")
|
||||
@@ -0,0 +1,26 @@
|
||||
menu "iBeacon Example Configuration"
|
||||
|
||||
choice IBEACON_MODE
|
||||
bool "iBeacon Mode"
|
||||
default IBEACON_SENDER
|
||||
help
|
||||
Select the iBeacon Mode.
|
||||
|
||||
config IBEACON_SENDER
|
||||
bool "iBeacon Sender Mode"
|
||||
help
|
||||
Select the iBeacon Sender Mode.
|
||||
|
||||
config IBEACON_RECEIVER
|
||||
bool "iBeacon Receiver Mode"
|
||||
help
|
||||
Select the iBeacon Receiver Mode.
|
||||
|
||||
endchoice
|
||||
|
||||
config IBEACON_MODE
|
||||
int
|
||||
default 0 if IBEACON_SENDER
|
||||
default 1 if IBEACON_RECEIVER
|
||||
|
||||
endmenu
|
||||
@@ -0,0 +1,4 @@
|
||||
#
|
||||
# "main" pseudo-component makefile.
|
||||
#
|
||||
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
|
||||
|
||||
/****************************************************************************
|
||||
*
|
||||
* This file is for iBeacon APIs. It supports both iBeacon encode and decode.
|
||||
*
|
||||
* iBeacon is a trademark of Apple Inc. Before building devices which use iBeacon technology,
|
||||
* visit https://developer.apple.com/ibeacon/ to obtain a license.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "esp_gap_ble_api.h"
|
||||
#include "esp_ibeacon_api.h"
|
||||
|
||||
|
||||
const uint8_t uuid_zeros[ESP_UUID_LEN_128] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
|
||||
/* For iBeacon packet format, please refer to Apple "Proximity Beacon Specification" doc */
|
||||
/* Constant part of iBeacon data */
|
||||
esp_ble_ibeacon_head_t ibeacon_common_head = {
|
||||
.flags = {0x02, 0x01, 0x06},
|
||||
.length = 0x1A,
|
||||
.type = 0xFF,
|
||||
.company_id = 0x004C,
|
||||
.beacon_type = 0x1502
|
||||
};
|
||||
|
||||
/* Vendor part of iBeacon data*/
|
||||
esp_ble_ibeacon_vendor_t vendor_config = {
|
||||
.proximity_uuid = ESP_UUID,
|
||||
.major = ENDIAN_CHANGE_U16(ESP_MAJOR), //Major=ESP_MAJOR
|
||||
.minor = ENDIAN_CHANGE_U16(ESP_MINOR), //Minor=ESP_MINOR
|
||||
.measured_power = 0xC5
|
||||
};
|
||||
|
||||
bool esp_ble_is_ibeacon_packet (uint8_t *adv_data, uint8_t adv_data_len){
|
||||
bool result = false;
|
||||
|
||||
if ((adv_data != NULL) && (adv_data_len == 0x1E)){
|
||||
if (!memcmp(adv_data, (uint8_t*)&ibeacon_common_head, sizeof(ibeacon_common_head))){
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
esp_err_t esp_ble_config_ibeacon_data (esp_ble_ibeacon_vendor_t *vendor_config, esp_ble_ibeacon_t *ibeacon_adv_data){
|
||||
if ((vendor_config == NULL) || (ibeacon_adv_data == NULL) || (!memcmp(vendor_config->proximity_uuid, uuid_zeros, sizeof(uuid_zeros)))){
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
memcpy(&ibeacon_adv_data->ibeacon_head, &ibeacon_common_head, sizeof(esp_ble_ibeacon_head_t));
|
||||
memcpy(&ibeacon_adv_data->ibeacon_vendor, vendor_config, sizeof(esp_ble_ibeacon_vendor_t));
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
|
||||
|
||||
/****************************************************************************
|
||||
*
|
||||
* This file is for iBeacon definitions. It supports both iBeacon sender and receiver
|
||||
* which is distinguished by macros IBEACON_SENDER and IBEACON_RECEIVER,
|
||||
*
|
||||
* iBeacon is a trademark of Apple Inc. Before building devices which use iBeacon technology,
|
||||
* visit https://developer.apple.com/ibeacon/ to obtain a license.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "esp_gap_ble_api.h"
|
||||
#include "esp_gattc_api.h"
|
||||
|
||||
|
||||
/* Because current ESP IDF version doesn't support scan and adv simultaneously,
|
||||
* so iBeacon sender and receiver should not run simultaneously */
|
||||
#define IBEACON_SENDER 0
|
||||
#define IBEACON_RECEIVER 1
|
||||
#define IBEACON_MODE CONFIG_IBEACON_MODE
|
||||
|
||||
/* Major and Minor part are stored in big endian mode in iBeacon packet,
|
||||
* need to use this macro to transfer while creating or processing
|
||||
* iBeacon data */
|
||||
#define ENDIAN_CHANGE_U16(x) ((((x)&0xFF00)>>8) + (((x)&0xFF)<<8))
|
||||
|
||||
/* Espressif WeChat official account can be found using WeChat "Yao Yi Yao Zhou Bian",
|
||||
* if device advertises using ESP defined UUID.
|
||||
* Please refer to http://zb.weixin.qq.com for further information. */
|
||||
#define ESP_UUID {0xFD, 0xA5, 0x06, 0x93, 0xA4, 0xE2, 0x4F, 0xB1, 0xAF, 0xCF, 0xC6, 0xEB, 0x07, 0x64, 0x78, 0x25}
|
||||
#define ESP_MAJOR 10167
|
||||
#define ESP_MINOR 61958
|
||||
|
||||
|
||||
typedef struct {
|
||||
uint8_t flags[3];
|
||||
uint8_t length;
|
||||
uint8_t type;
|
||||
uint16_t company_id;
|
||||
uint16_t beacon_type;
|
||||
}__attribute__((packed)) esp_ble_ibeacon_head_t;
|
||||
|
||||
typedef struct {
|
||||
uint8_t proximity_uuid[16];
|
||||
uint16_t major;
|
||||
uint16_t minor;
|
||||
int8_t measured_power;
|
||||
}__attribute__((packed)) esp_ble_ibeacon_vendor_t;
|
||||
|
||||
|
||||
typedef struct {
|
||||
esp_ble_ibeacon_head_t ibeacon_head;
|
||||
esp_ble_ibeacon_vendor_t ibeacon_vendor;
|
||||
}__attribute__((packed)) esp_ble_ibeacon_t;
|
||||
|
||||
|
||||
/* For iBeacon packet format, please refer to Apple "Proximity Beacon Specification" doc */
|
||||
/* Constant part of iBeacon data */
|
||||
extern esp_ble_ibeacon_head_t ibeacon_common_head;
|
||||
|
||||
bool esp_ble_is_ibeacon_packet (uint8_t *adv_data, uint8_t adv_data_len);
|
||||
|
||||
esp_err_t esp_ble_config_ibeacon_data (esp_ble_ibeacon_vendor_t *vendor_config, esp_ble_ibeacon_t *ibeacon_adv_data);
|
||||
@@ -0,0 +1,191 @@
|
||||
/*
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
|
||||
|
||||
/****************************************************************************
|
||||
*
|
||||
* This file is for iBeacon demo. It supports both iBeacon sender and receiver
|
||||
* which is distinguished by macros IBEACON_SENDER and IBEACON_RECEIVER,
|
||||
*
|
||||
* iBeacon is a trademark of Apple Inc. Before building devices which use iBeacon technology,
|
||||
* visit https://developer.apple.com/ibeacon/ to obtain a license.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include "nvs_flash.h"
|
||||
|
||||
#include "esp_bt.h"
|
||||
#include "esp_gap_ble_api.h"
|
||||
#include "esp_gattc_api.h"
|
||||
#include "esp_gatt_defs.h"
|
||||
#include "esp_bt_main.h"
|
||||
#include "esp_bt_defs.h"
|
||||
#include "esp_ibeacon_api.h"
|
||||
#include "esp_log.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
|
||||
static const char* DEMO_TAG = "IBEACON_DEMO";
|
||||
extern esp_ble_ibeacon_vendor_t vendor_config;
|
||||
|
||||
///Declare static functions
|
||||
static void esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param);
|
||||
|
||||
#if (IBEACON_MODE == IBEACON_RECEIVER)
|
||||
static esp_ble_scan_params_t ble_scan_params = {
|
||||
.scan_type = BLE_SCAN_TYPE_ACTIVE,
|
||||
.own_addr_type = BLE_ADDR_TYPE_PUBLIC,
|
||||
.scan_filter_policy = BLE_SCAN_FILTER_ALLOW_ALL,
|
||||
.scan_interval = 0x50,
|
||||
.scan_window = 0x30,
|
||||
.scan_duplicate = BLE_SCAN_DUPLICATE_DISABLE
|
||||
};
|
||||
|
||||
#elif (IBEACON_MODE == IBEACON_SENDER)
|
||||
static esp_ble_adv_params_t ble_adv_params = {
|
||||
.adv_int_min = 0x20,
|
||||
.adv_int_max = 0x40,
|
||||
.adv_type = ADV_TYPE_NONCONN_IND,
|
||||
.own_addr_type = BLE_ADDR_TYPE_PUBLIC,
|
||||
.channel_map = ADV_CHNL_ALL,
|
||||
.adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY,
|
||||
};
|
||||
#endif
|
||||
|
||||
|
||||
static void esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param)
|
||||
{
|
||||
esp_err_t err;
|
||||
|
||||
switch (event) {
|
||||
case ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT:{
|
||||
#if (IBEACON_MODE == IBEACON_SENDER)
|
||||
esp_ble_gap_start_advertising(&ble_adv_params);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT: {
|
||||
#if (IBEACON_MODE == IBEACON_RECEIVER)
|
||||
//the unit of the duration is second, 0 means scan permanently
|
||||
uint32_t duration = 0;
|
||||
esp_ble_gap_start_scanning(duration);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
case ESP_GAP_BLE_SCAN_START_COMPLETE_EVT:
|
||||
//scan start complete event to indicate scan start successfully or failed
|
||||
if ((err = param->scan_start_cmpl.status) != ESP_BT_STATUS_SUCCESS) {
|
||||
ESP_LOGE(DEMO_TAG, "Scan start failed: %s", esp_err_to_name(err));
|
||||
}
|
||||
break;
|
||||
case ESP_GAP_BLE_ADV_START_COMPLETE_EVT:
|
||||
//adv start complete event to indicate adv start successfully or failed
|
||||
if ((err = param->adv_start_cmpl.status) != ESP_BT_STATUS_SUCCESS) {
|
||||
ESP_LOGE(DEMO_TAG, "Adv start failed: %s", esp_err_to_name(err));
|
||||
}
|
||||
break;
|
||||
case ESP_GAP_BLE_SCAN_RESULT_EVT: {
|
||||
esp_ble_gap_cb_param_t *scan_result = (esp_ble_gap_cb_param_t *)param;
|
||||
switch (scan_result->scan_rst.search_evt) {
|
||||
case ESP_GAP_SEARCH_INQ_RES_EVT:
|
||||
/* Search for BLE iBeacon Packet */
|
||||
if (esp_ble_is_ibeacon_packet(scan_result->scan_rst.ble_adv, scan_result->scan_rst.adv_data_len)){
|
||||
esp_ble_ibeacon_t *ibeacon_data = (esp_ble_ibeacon_t*)(scan_result->scan_rst.ble_adv);
|
||||
ESP_LOGI(DEMO_TAG, "----------iBeacon Found----------");
|
||||
esp_log_buffer_hex("IBEACON_DEMO: Device address:", scan_result->scan_rst.bda, ESP_BD_ADDR_LEN );
|
||||
esp_log_buffer_hex("IBEACON_DEMO: Proximity UUID:", ibeacon_data->ibeacon_vendor.proximity_uuid, ESP_UUID_LEN_128);
|
||||
|
||||
uint16_t major = ENDIAN_CHANGE_U16(ibeacon_data->ibeacon_vendor.major);
|
||||
uint16_t minor = ENDIAN_CHANGE_U16(ibeacon_data->ibeacon_vendor.minor);
|
||||
ESP_LOGI(DEMO_TAG, "Major: 0x%04x (%d)", major, major);
|
||||
ESP_LOGI(DEMO_TAG, "Minor: 0x%04x (%d)", minor, minor);
|
||||
ESP_LOGI(DEMO_TAG, "Measured power (RSSI at a 1m distance):%d dbm", ibeacon_data->ibeacon_vendor.measured_power);
|
||||
ESP_LOGI(DEMO_TAG, "RSSI of packet:%d dbm", scan_result->scan_rst.rssi);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT:
|
||||
if ((err = param->scan_stop_cmpl.status) != ESP_BT_STATUS_SUCCESS){
|
||||
ESP_LOGE(DEMO_TAG, "Scan stop failed: %s", esp_err_to_name(err));
|
||||
}
|
||||
else {
|
||||
ESP_LOGI(DEMO_TAG, "Stop scan successfully");
|
||||
}
|
||||
break;
|
||||
|
||||
case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT:
|
||||
if ((err = param->adv_stop_cmpl.status) != ESP_BT_STATUS_SUCCESS){
|
||||
ESP_LOGE(DEMO_TAG, "Adv stop failed: %s", esp_err_to_name(err));
|
||||
}
|
||||
else {
|
||||
ESP_LOGI(DEMO_TAG, "Stop adv successfully");
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void ble_ibeacon_appRegister(void)
|
||||
{
|
||||
esp_err_t status;
|
||||
|
||||
ESP_LOGI(DEMO_TAG, "register callback");
|
||||
|
||||
//register the scan callback function to the gap module
|
||||
if ((status = esp_ble_gap_register_callback(esp_gap_cb)) != ESP_OK) {
|
||||
ESP_LOGE(DEMO_TAG, "gap register error: %s", esp_err_to_name(status));
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void ble_ibeacon_init(void)
|
||||
{
|
||||
esp_bluedroid_init();
|
||||
esp_bluedroid_enable();
|
||||
ble_ibeacon_appRegister();
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
ESP_ERROR_CHECK(nvs_flash_init());
|
||||
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_bt_controller_init(&bt_cfg);
|
||||
esp_bt_controller_enable(ESP_BT_MODE_BLE);
|
||||
|
||||
ble_ibeacon_init();
|
||||
|
||||
/* set scan parameters */
|
||||
#if (IBEACON_MODE == IBEACON_RECEIVER)
|
||||
esp_ble_gap_set_scan_params(&ble_scan_params);
|
||||
|
||||
#elif (IBEACON_MODE == IBEACON_SENDER)
|
||||
esp_ble_ibeacon_t ibeacon_adv_data;
|
||||
esp_err_t status = esp_ble_config_ibeacon_data (&vendor_config, &ibeacon_adv_data);
|
||||
if (status == ESP_OK){
|
||||
esp_ble_gap_config_adv_data_raw((uint8_t*)&ibeacon_adv_data, sizeof(ibeacon_adv_data));
|
||||
}
|
||||
else {
|
||||
ESP_LOGE(DEMO_TAG, "Config iBeacon data failed: %s\n", esp_err_to_name(status));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
# Override some defaults so BT stack is enabled
|
||||
# and WiFi disabled by default in this example
|
||||
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
|
||||
@@ -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(spp_client_demo)
|
||||
@@ -0,0 +1,10 @@
|
||||
#
|
||||
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
|
||||
# project subdirectory.
|
||||
#
|
||||
|
||||
PROJECT_NAME := spp_client_demo
|
||||
|
||||
COMPONENT_ADD_INCLUDEDIRS := components/include
|
||||
|
||||
include $(IDF_PATH)/make/project.mk
|
||||
@@ -0,0 +1,156 @@
|
||||
| Supported Targets | ESP32 |
|
||||
| ----------------- | ----- |
|
||||
|
||||
# ESP-IDF SPP GATT CLIENT demo
|
||||
|
||||
In Bluetooth classic (BR/EDR) systems, a Serial Port Profile (SPP) is an adopted profile defined by the Bluetooth Special Interest Group (SIG) used to emulate a serial port connection over a Bluetooth wireless connection. For BLE systems, an adopted SPP profile over BLE is not defined, thus emulation of a serial port must be implemented as a vendor-specific custom profile.
|
||||
|
||||
This reference design consists of two Demos, the ble spp server and ble spp client that run on their respective endpoints. These devices connect and exchange data wirelessly with each other. This capability creates a virtual serial link over the air. Each byte input can be sent and received by both the server and client. The spp server is implemented as the [ble_spp_server](../ble_spp_server) demo while the spp client is implemented as the [ble_spp_client](../ble_spp_client) demo. Espressif designed the BLE SPP applications to use the UART transport layer but you could adapt this design to work with other serial protocols, such as SPI.
|
||||
|
||||
This vendor-specific custom profile is implemented in [spp_client_demo.c](../ble_spp_client/main/spp_client_demo.c) and [spp_server_demo.c](../ble_spp_server/main/ble_spp_server_demo.c).
|
||||
|
||||
## Using Examples
|
||||
|
||||
### Initialization
|
||||
|
||||
Both the server and client will first initialize the uart and ble. The server demo will set up the serial port service with standard GATT and GAP services in the attribute server. The client demo will scan the ble broadcast over the air to find the spp server.
|
||||
|
||||
### Event Processing
|
||||
|
||||
The spp server has two main event processing functions for BLE event:
|
||||
|
||||
```c
|
||||
void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t * param);
|
||||
void gatts_profile_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t * param);
|
||||
```
|
||||
|
||||
The spp client has two main event processing functions for BLE event:
|
||||
|
||||
```c
|
||||
esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t * param);
|
||||
void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t * param);
|
||||
```
|
||||
|
||||
These are some queues and tasks used by SPP application:
|
||||
|
||||
Queues:
|
||||
|
||||
* spp_uart_queue - Uart data messages received from the Uart
|
||||
* cmd_cmd_queue - commands received from the client
|
||||
* cmd_heartbeat_queue - heartbeat received, if supported
|
||||
|
||||
Tasks:
|
||||
|
||||
* `uart_task` - process Uart
|
||||
* `spp_cmd_task` - process command messages, the commands and processing were defined by customer
|
||||
* `spp_heartbeat_task` - if heartbeat is supported, the task will send a heatbeat packet to the remote device
|
||||
|
||||
### Packet Structure
|
||||
|
||||
After the Uart received data, the data will be posted to Uart task. Then, in the UART_DATA event, the raw data may be retrieved. The max length is 120 bytes every time.
|
||||
If you run the ble spp demo with two ESP32 chips, the MTU size will be exchanged for 200 bytes after the ble connection is established, so every packet can be send directly.
|
||||
If you only run the ble_spp_server demo, and it was connected by a phone, the MTU size may be less than 123 bytes. In such a case the data will be split into fragments and send in turn.
|
||||
In every packet, we add 4 bytes to indicate that this is a fragment packet. The first two bytes contain "##" if this is a fragment packet, the third byte is the total number of the packets, the fourth byte is the current number of this packet.
|
||||
The phone APP need to check the structure of the packet if it want to communicate with the ble_spp_server demo.
|
||||
|
||||
### Sending Data Wirelessly
|
||||
|
||||
The client will be sending WriteNoRsp packets to the server. The server side sends data through notifications. When the Uart receives data, the Uart task places it in the buffer. If the size of the data is larger than (MTU size - 3), the data will be split into packets and send in turn.
|
||||
|
||||
### Receiving Data Wirelessly
|
||||
|
||||
The server will receive this data in the ESP_GATTS_WRITE_EVT event and send data to the Uart terminal by `uart_wrire_bytes` function. For example:
|
||||
|
||||
case ESP_GATTS_WRITE_EVT:
|
||||
...
|
||||
if(res == SPP_IDX_SPP_DATA_RECV_VAL){
|
||||
uart_write_bytes(UART_NUM_0, (char *)(p_data->write.value), p_data->write.len);
|
||||
}
|
||||
...
|
||||
break;
|
||||
|
||||
### GATT Server Attribute Table
|
||||
|
||||
charactertistic|UUID|Permissions
|
||||
:-:|:-:|:-:
|
||||
SPP_DATA_RECV_CHAR|0xABF1|READ&WRITE_NR
|
||||
SPP_DATA_NOTIFY_CHAR|0xABF2|READ&NOTIFY
|
||||
SPP_COMMAND_CHAR|0xABF3|READ&WRITE_NR
|
||||
SPP_STATUS_CHAR|0xABF4|READ & NOTIFY
|
||||
SPP_HEARTBEAT_CHAR|0xABF5|READ&WRITE_NR&NOTIFY
|
||||
|
||||
### Build and Flash
|
||||
|
||||
Build each project and flash it to the board, then run monitor tool to view serial output:
|
||||
|
||||
```
|
||||
idf.py -p PORT flash monitor
|
||||
```
|
||||
|
||||
(To exit the serial monitor, type ``Ctrl-]``.)
|
||||
|
||||
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
|
||||
|
||||
## Example Output
|
||||
|
||||
The spp cilent will auto connect to the spp server, do service search, exchange MTU size and register notification.
|
||||
|
||||
### Client
|
||||
|
||||
```
|
||||
I (2894) GATTC_SPP_DEMO: ESP_GATTC_CONNECT_EVT: conn_id=0, gatt_if = 3
|
||||
I (2894) GATTC_SPP_DEMO: REMOTE BDA:
|
||||
I (2904) GATTC_SPP_DEMO: 00 00 00 00 00 00
|
||||
I (2904) GATTC_SPP_DEMO: EVT 2, gattc if 3
|
||||
I (3414) GATTC_SPP_DEMO: EVT 7, gattc if 3
|
||||
I (3414) GATTC_SPP_DEMO: ESP_GATTC_SEARCH_RES_EVT: start_handle = 40, end_handle = 65535, UUID:0xabf0
|
||||
I (3424) GATTC_SPP_DEMO: EVT 6, gattc if 3
|
||||
I (3424) GATTC_SPP_DEMO: SEARCH_CMPL: conn_id = 0, status 0
|
||||
I (3464) GATTC_SPP_DEMO: EVT 18, gattc if 3
|
||||
I (3464) GATTC_SPP_DEMO: +MTU:200
|
||||
|
||||
I (3464) GATTC_SPP_DEMO: attr_type = PRIMARY_SERVICE,attribute_handle=40,start_handle=40,end_handle=65535,properties=0x0,uuid=0xabf0
|
||||
I (3474) GATTC_SPP_DEMO: attr_type = CHARACTERISTIC,attribute_handle=42,start_handle=0,end_handle=0,properties=0x6,uuid=0xabf1
|
||||
I (3484) GATTC_SPP_DEMO: attr_type = CHARACTERISTIC,attribute_handle=44,start_handle=0,end_handle=0,properties=0x12,uuid=0xabf2
|
||||
I (3494) GATTC_SPP_DEMO: attr_type = DESCRIPTOR,attribute_handle=45,start_handle=0,end_handle=0,properties=0x0,uuid=0x2902
|
||||
I (3504) GATTC_SPP_DEMO: attr_type = CHARACTERISTIC,attribute_handle=47,start_handle=0,end_handle=0,properties=0x6,uuid=0xabf3
|
||||
I (3524) GATTC_SPP_DEMO: attr_type = CHARACTERISTIC,attribute_handle=49,start_handle=0,end_handle=0,properties=0x12,uuid=0xabf4
|
||||
I (3534) GATTC_SPP_DEMO: attr_type = DESCRIPTOR,attribute_handle=50,start_handle=0,end_handle=0,properties=0x0,uuid=0x2902
|
||||
I (3544) GATTC_SPP_DEMO: Index = 2,UUID = 0xabf2, handle = 44
|
||||
I (3554) GATTC_SPP_DEMO: EVT 38, gattc if 3
|
||||
I (3554) GATTC_SPP_DEMO: Index = 2,status = 0,handle = 44
|
||||
I (3594) GATTC_SPP_DEMO: EVT 9, gattc if 3
|
||||
I (3594) GATTC_SPP_DEMO: ESP_GATTC_WRITE_DESCR_EVT: status =0,handle = 45
|
||||
I (3654) GATTC_SPP_DEMO: Index = 5,UUID = 0xabf4, handle = 49
|
||||
I (3654) GATTC_SPP_DEMO: EVT 38, gattc if 3
|
||||
I (3654) GATTC_SPP_DEMO: Index = 5,status = 0,handle = 49
|
||||
I (3684) GATTC_SPP_DEMO: EVT 9, gattc if 3
|
||||
I (3684) GATTC_SPP_DEMO: ESP_GATTC_WRITE_DESCR_EVT: status =0,handle = 50
|
||||
I (16904) GATTC_SPP_DEMO: EVT 10, gattc if 3
|
||||
I (16904) GATTC_SPP_DEMO: ESP_GATTC_NOTIFY_EVT
|
||||
I (16904) GATTC_SPP_DEMO: +NOTIFY:handle = 44,length = 22
|
||||
```
|
||||
|
||||
### Server
|
||||
|
||||
```
|
||||
I (4452) GATTS_SPP_DEMO: EVT 14, gatts if 3
|
||||
I (4452) GATTS_SPP_DEMO: event = e
|
||||
I (5022) GATTS_SPP_DEMO: EVT 4, gatts if 3
|
||||
I (5022) GATTS_SPP_DEMO: event = 4
|
||||
I (5152) GATTS_SPP_DEMO: EVT 2, gatts if 3
|
||||
I (5152) GATTS_SPP_DEMO: event = 2
|
||||
I (5152) GATTS_SPP_DEMO: ESP_GATTS_WRITE_EVT : handle = 5
|
||||
I (5242) GATTS_SPP_DEMO: EVT 2, gatts if 3
|
||||
I (5242) GATTS_SPP_DEMO: event = 2
|
||||
I (5242) GATTS_SPP_DEMO: ESP_GATTS_WRITE_EVT : handle = 10
|
||||
I (18462) GATTS_SPP_DEMO: EVT 5, gatts if 3
|
||||
I (18462) GATTS_SPP_DEMO: event = 5
|
||||
I (27652) GATTS_SPP_DEMO: EVT 2, gatts if 3
|
||||
I (27652) GATTS_SPP_DEMO: event = 2
|
||||
I (27652) GATTS_SPP_DEMO: ESP_GATTS_WRITE_EVT : handle = 2
|
||||
|
||||
```
|
||||
if you input data to the Uart terminal, it will be printed in the remote device Uart terminal.
|
||||
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
idf_component_register(SRCS "spp_client_demo.c"
|
||||
INCLUDE_DIRS ".")
|
||||
@@ -0,0 +1,4 @@
|
||||
#
|
||||
# "main" pseudo-component makefile.
|
||||
#
|
||||
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
|
||||
@@ -0,0 +1,643 @@
|
||||
/*
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
/****************************************************************************
|
||||
*
|
||||
* This file is for ble spp client demo.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include "driver/uart.h"
|
||||
|
||||
#include "esp_bt.h"
|
||||
#include "nvs_flash.h"
|
||||
#include "esp_bt_device.h"
|
||||
#include "esp_gap_ble_api.h"
|
||||
#include "esp_gattc_api.h"
|
||||
#include "esp_gatt_defs.h"
|
||||
#include "esp_bt_main.h"
|
||||
#include "esp_system.h"
|
||||
#include "esp_gatt_common_api.h"
|
||||
#include "esp_log.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
|
||||
#define GATTC_TAG "GATTC_SPP_DEMO"
|
||||
#define PROFILE_NUM 1
|
||||
#define PROFILE_APP_ID 0
|
||||
#define BT_BD_ADDR_STR "%02x:%02x:%02x:%02x:%02x:%02x"
|
||||
#define BT_BD_ADDR_HEX(addr) addr[0],addr[1],addr[2],addr[3],addr[4],addr[5]
|
||||
#define ESP_GATT_SPP_SERVICE_UUID 0xABF0
|
||||
#define SCAN_ALL_THE_TIME 0
|
||||
|
||||
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 char_handle;
|
||||
esp_bd_addr_t remote_bda;
|
||||
};
|
||||
|
||||
enum{
|
||||
SPP_IDX_SVC,
|
||||
|
||||
SPP_IDX_SPP_DATA_RECV_VAL,
|
||||
|
||||
SPP_IDX_SPP_DATA_NTY_VAL,
|
||||
SPP_IDX_SPP_DATA_NTF_CFG,
|
||||
|
||||
SPP_IDX_SPP_COMMAND_VAL,
|
||||
|
||||
SPP_IDX_SPP_STATUS_VAL,
|
||||
SPP_IDX_SPP_STATUS_CFG,
|
||||
|
||||
#ifdef SUPPORT_HEARTBEAT
|
||||
SPP_IDX_SPP_HEARTBEAT_VAL,
|
||||
SPP_IDX_SPP_HEARTBEAT_CFG,
|
||||
#endif
|
||||
|
||||
SPP_IDX_NB,
|
||||
};
|
||||
|
||||
///Declare static functions
|
||||
static void esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param);
|
||||
static void esp_gattc_cb(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param);
|
||||
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);
|
||||
|
||||
/* One gatt-based profile one app_id and one gattc_if, this array will store the gattc_if returned by ESP_GATTS_REG_EVT */
|
||||
static struct gattc_profile_inst gl_profile_tab[PROFILE_NUM] = {
|
||||
[PROFILE_APP_ID] = {
|
||||
.gattc_cb = gattc_profile_event_handler,
|
||||
.gattc_if = ESP_GATT_IF_NONE, /* Not get the gatt_if, so initial is ESP_GATT_IF_NONE */
|
||||
},
|
||||
};
|
||||
|
||||
static esp_ble_scan_params_t ble_scan_params = {
|
||||
.scan_type = BLE_SCAN_TYPE_ACTIVE,
|
||||
.own_addr_type = BLE_ADDR_TYPE_PUBLIC,
|
||||
.scan_filter_policy = BLE_SCAN_FILTER_ALLOW_ALL,
|
||||
.scan_interval = 0x50,
|
||||
.scan_window = 0x30,
|
||||
.scan_duplicate = BLE_SCAN_DUPLICATE_DISABLE
|
||||
};
|
||||
|
||||
static const char device_name[] = "ESP_SPP_SERVER";
|
||||
static bool is_connect = false;
|
||||
static uint16_t spp_conn_id = 0;
|
||||
static uint16_t spp_mtu_size = 23;
|
||||
static uint16_t cmd = 0;
|
||||
static uint16_t spp_srv_start_handle = 0;
|
||||
static uint16_t spp_srv_end_handle = 0;
|
||||
static uint16_t spp_gattc_if = 0xff;
|
||||
static char * notify_value_p = NULL;
|
||||
static int notify_value_offset = 0;
|
||||
static int notify_value_count = 0;
|
||||
static uint16_t count = SPP_IDX_NB;
|
||||
static esp_gattc_db_elem_t *db = NULL;
|
||||
static esp_ble_gap_cb_param_t scan_rst;
|
||||
static xQueueHandle cmd_reg_queue = NULL;
|
||||
QueueHandle_t spp_uart_queue = NULL;
|
||||
|
||||
#ifdef SUPPORT_HEARTBEAT
|
||||
static uint8_t heartbeat_s[9] = {'E','s','p','r','e','s','s','i','f'};
|
||||
static xQueueHandle cmd_heartbeat_queue = NULL;
|
||||
#endif
|
||||
|
||||
static esp_bt_uuid_t spp_service_uuid = {
|
||||
.len = ESP_UUID_LEN_16,
|
||||
.uuid = {.uuid16 = ESP_GATT_SPP_SERVICE_UUID,},
|
||||
};
|
||||
|
||||
static void notify_event_handler(esp_ble_gattc_cb_param_t * p_data)
|
||||
{
|
||||
uint8_t handle = 0;
|
||||
|
||||
if(p_data->notify.is_notify == true){
|
||||
ESP_LOGI(GATTC_TAG,"+NOTIFY:handle = %d,length = %d ", p_data->notify.handle, p_data->notify.value_len);
|
||||
}else{
|
||||
ESP_LOGI(GATTC_TAG,"+INDICATE:handle = %d,length = %d ", p_data->notify.handle, p_data->notify.value_len);
|
||||
}
|
||||
handle = p_data->notify.handle;
|
||||
if(db == NULL) {
|
||||
ESP_LOGE(GATTC_TAG, " %s db is NULL\n", __func__);
|
||||
return;
|
||||
}
|
||||
if(handle == db[SPP_IDX_SPP_DATA_NTY_VAL].attribute_handle){
|
||||
#ifdef SPP_DEBUG_MODE
|
||||
esp_log_buffer_char(GATTC_TAG, (char *)p_data->notify.value, p_data->notify.value_len);
|
||||
#else
|
||||
if((p_data->notify.value[0] == '#')&&(p_data->notify.value[1] == '#')){
|
||||
if((++notify_value_count) != p_data->notify.value[3]){
|
||||
if(notify_value_p != NULL){
|
||||
free(notify_value_p);
|
||||
}
|
||||
notify_value_count = 0;
|
||||
notify_value_p = NULL;
|
||||
notify_value_offset = 0;
|
||||
ESP_LOGE(GATTC_TAG,"notify value count is not continuous,%s\n",__func__);
|
||||
return;
|
||||
}
|
||||
if(p_data->notify.value[3] == 1){
|
||||
notify_value_p = (char *)malloc(((spp_mtu_size-7)*(p_data->notify.value[2]))*sizeof(char));
|
||||
if(notify_value_p == NULL){
|
||||
ESP_LOGE(GATTC_TAG, "malloc failed,%s L#%d\n",__func__,__LINE__);
|
||||
notify_value_count = 0;
|
||||
return;
|
||||
}
|
||||
memcpy((notify_value_p + notify_value_offset),(p_data->notify.value + 4),(p_data->notify.value_len - 4));
|
||||
if(p_data->notify.value[2] == p_data->notify.value[3]){
|
||||
uart_write_bytes(UART_NUM_0, (char *)(notify_value_p), (p_data->notify.value_len - 4 + notify_value_offset));
|
||||
free(notify_value_p);
|
||||
notify_value_p = NULL;
|
||||
notify_value_offset = 0;
|
||||
return;
|
||||
}
|
||||
notify_value_offset += (p_data->notify.value_len - 4);
|
||||
}else if(p_data->notify.value[3] <= p_data->notify.value[2]){
|
||||
memcpy((notify_value_p + notify_value_offset),(p_data->notify.value + 4),(p_data->notify.value_len - 4));
|
||||
if(p_data->notify.value[3] == p_data->notify.value[2]){
|
||||
uart_write_bytes(UART_NUM_0, (char *)(notify_value_p), (p_data->notify.value_len - 4 + notify_value_offset));
|
||||
free(notify_value_p);
|
||||
notify_value_count = 0;
|
||||
notify_value_p = NULL;
|
||||
notify_value_offset = 0;
|
||||
return;
|
||||
}
|
||||
notify_value_offset += (p_data->notify.value_len - 4);
|
||||
}
|
||||
}else{
|
||||
uart_write_bytes(UART_NUM_0, (char *)(p_data->notify.value), p_data->notify.value_len);
|
||||
}
|
||||
#endif
|
||||
}else if(handle == ((db+SPP_IDX_SPP_STATUS_VAL)->attribute_handle)){
|
||||
esp_log_buffer_char(GATTC_TAG, (char *)p_data->notify.value, p_data->notify.value_len);
|
||||
//TODO:server notify status characteristic
|
||||
}else{
|
||||
esp_log_buffer_char(GATTC_TAG, (char *)p_data->notify.value, p_data->notify.value_len);
|
||||
}
|
||||
}
|
||||
|
||||
static void free_gattc_srv_db(void)
|
||||
{
|
||||
is_connect = false;
|
||||
spp_gattc_if = 0xff;
|
||||
spp_conn_id = 0;
|
||||
spp_mtu_size = 23;
|
||||
cmd = 0;
|
||||
spp_srv_start_handle = 0;
|
||||
spp_srv_end_handle = 0;
|
||||
notify_value_p = NULL;
|
||||
notify_value_offset = 0;
|
||||
notify_value_count = 0;
|
||||
if(db){
|
||||
free(db);
|
||||
db = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static void esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param)
|
||||
{
|
||||
uint8_t *adv_name = NULL;
|
||||
uint8_t adv_name_len = 0;
|
||||
esp_err_t err;
|
||||
|
||||
switch(event){
|
||||
case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT: {
|
||||
if((err = param->scan_param_cmpl.status) != ESP_BT_STATUS_SUCCESS){
|
||||
ESP_LOGE(GATTC_TAG, "Scan param set failed: %s", esp_err_to_name(err));
|
||||
break;
|
||||
}
|
||||
//the unit of the duration is second
|
||||
uint32_t duration = 0xFFFF;
|
||||
ESP_LOGI(GATTC_TAG, "Enable Ble Scan:during time 0x%04X minutes.",duration);
|
||||
esp_ble_gap_start_scanning(duration);
|
||||
break;
|
||||
}
|
||||
case ESP_GAP_BLE_SCAN_START_COMPLETE_EVT:
|
||||
//scan start complete event to indicate scan start successfully or failed
|
||||
if ((err = param->scan_start_cmpl.status) != ESP_BT_STATUS_SUCCESS) {
|
||||
ESP_LOGE(GATTC_TAG, "Scan start failed: %s", esp_err_to_name(err));
|
||||
break;
|
||||
}
|
||||
ESP_LOGI(GATTC_TAG, "Scan start successed");
|
||||
break;
|
||||
case ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT:
|
||||
if ((err = param->scan_stop_cmpl.status) != ESP_BT_STATUS_SUCCESS) {
|
||||
ESP_LOGE(GATTC_TAG, "Scan stop failed: %s", esp_err_to_name(err));
|
||||
break;
|
||||
}
|
||||
ESP_LOGI(GATTC_TAG, "Scan stop successed");
|
||||
if (is_connect == false) {
|
||||
ESP_LOGI(GATTC_TAG, "Connect to the remote device.");
|
||||
esp_ble_gattc_open(gl_profile_tab[PROFILE_APP_ID].gattc_if, scan_rst.scan_rst.bda, scan_rst.scan_rst.ble_addr_type, true);
|
||||
}
|
||||
break;
|
||||
case ESP_GAP_BLE_SCAN_RESULT_EVT: {
|
||||
esp_ble_gap_cb_param_t *scan_result = (esp_ble_gap_cb_param_t *)param;
|
||||
switch (scan_result->scan_rst.search_evt) {
|
||||
case ESP_GAP_SEARCH_INQ_RES_EVT:
|
||||
esp_log_buffer_hex(GATTC_TAG, scan_result->scan_rst.bda, 6);
|
||||
ESP_LOGI(GATTC_TAG, "Searched Adv Data Len %d, Scan Response Len %d", scan_result->scan_rst.adv_data_len, scan_result->scan_rst.scan_rsp_len);
|
||||
adv_name = esp_ble_resolve_adv_data(scan_result->scan_rst.ble_adv, ESP_BLE_AD_TYPE_NAME_CMPL, &adv_name_len);
|
||||
ESP_LOGI(GATTC_TAG, "Searched Device Name Len %d", adv_name_len);
|
||||
esp_log_buffer_char(GATTC_TAG, adv_name, adv_name_len);
|
||||
ESP_LOGI(GATTC_TAG, "\n");
|
||||
if (adv_name != NULL) {
|
||||
if ( strncmp((char *)adv_name, device_name, adv_name_len) == 0) {
|
||||
memcpy(&(scan_rst), scan_result, sizeof(esp_ble_gap_cb_param_t));
|
||||
esp_ble_gap_stop_scanning();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ESP_GAP_SEARCH_INQ_CMPL_EVT:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT:
|
||||
if ((err = param->adv_stop_cmpl.status) != ESP_BT_STATUS_SUCCESS){
|
||||
ESP_LOGE(GATTC_TAG, "Adv stop failed: %s", esp_err_to_name(err));
|
||||
}else {
|
||||
ESP_LOGI(GATTC_TAG, "Stop adv successfully");
|
||||
}
|
||||
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)
|
||||
{
|
||||
ESP_LOGI(GATTC_TAG, "EVT %d, gattc if %d", event, gattc_if);
|
||||
|
||||
/* If event is register event, store the gattc_if for each profile */
|
||||
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(GATTC_TAG, "Reg app failed, app_id %04x, status %d", param->reg.app_id, param->reg.status);
|
||||
return;
|
||||
}
|
||||
}
|
||||
/* If the gattc_if equal to profile A, call profile A cb handler,
|
||||
* so here call each profile's callback */
|
||||
do {
|
||||
int idx;
|
||||
for (idx = 0; idx < PROFILE_NUM; idx++) {
|
||||
if (gattc_if == ESP_GATT_IF_NONE || /* ESP_GATT_IF_NONE, not specify a certain gatt_if, need to call every profile cb function */
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
} while (0);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
esp_ble_gattc_cb_param_t *p_data = (esp_ble_gattc_cb_param_t *)param;
|
||||
|
||||
switch (event) {
|
||||
case ESP_GATTC_REG_EVT:
|
||||
ESP_LOGI(GATTC_TAG, "REG EVT, set scan params");
|
||||
esp_ble_gap_set_scan_params(&ble_scan_params);
|
||||
break;
|
||||
case ESP_GATTC_CONNECT_EVT:
|
||||
ESP_LOGI(GATTC_TAG, "ESP_GATTC_CONNECT_EVT: conn_id=%d, gatt_if = %d", spp_conn_id, gattc_if);
|
||||
ESP_LOGI(GATTC_TAG, "REMOTE BDA:");
|
||||
esp_log_buffer_hex(GATTC_TAG, gl_profile_tab[PROFILE_APP_ID].remote_bda, sizeof(esp_bd_addr_t));
|
||||
spp_gattc_if = gattc_if;
|
||||
is_connect = true;
|
||||
spp_conn_id = p_data->connect.conn_id;
|
||||
memcpy(gl_profile_tab[PROFILE_APP_ID].remote_bda, p_data->connect.remote_bda, sizeof(esp_bd_addr_t));
|
||||
esp_ble_gattc_search_service(spp_gattc_if, spp_conn_id, &spp_service_uuid);
|
||||
break;
|
||||
case ESP_GATTC_DISCONNECT_EVT:
|
||||
ESP_LOGI(GATTC_TAG, "disconnect");
|
||||
free_gattc_srv_db();
|
||||
esp_ble_gap_start_scanning(SCAN_ALL_THE_TIME);
|
||||
break;
|
||||
case ESP_GATTC_SEARCH_RES_EVT:
|
||||
ESP_LOGI(GATTC_TAG, "ESP_GATTC_SEARCH_RES_EVT: start_handle = %d, end_handle = %d, UUID:0x%04x",p_data->search_res.start_handle,p_data->search_res.end_handle,p_data->search_res.srvc_id.uuid.uuid.uuid16);
|
||||
spp_srv_start_handle = p_data->search_res.start_handle;
|
||||
spp_srv_end_handle = p_data->search_res.end_handle;
|
||||
break;
|
||||
case ESP_GATTC_SEARCH_CMPL_EVT:
|
||||
ESP_LOGI(GATTC_TAG, "SEARCH_CMPL: conn_id = %x, status %d", spp_conn_id, p_data->search_cmpl.status);
|
||||
esp_ble_gattc_send_mtu_req(gattc_if, spp_conn_id);
|
||||
break;
|
||||
case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
|
||||
ESP_LOGI(GATTC_TAG,"Index = %d,status = %d,handle = %d\n",cmd, p_data->reg_for_notify.status, p_data->reg_for_notify.handle);
|
||||
if(p_data->reg_for_notify.status != ESP_GATT_OK){
|
||||
ESP_LOGE(GATTC_TAG, "ESP_GATTC_REG_FOR_NOTIFY_EVT, status = %d", p_data->reg_for_notify.status);
|
||||
break;
|
||||
}
|
||||
uint16_t notify_en = 1;
|
||||
esp_ble_gattc_write_char_descr(
|
||||
spp_gattc_if,
|
||||
spp_conn_id,
|
||||
(db+cmd+1)->attribute_handle,
|
||||
sizeof(notify_en),
|
||||
(uint8_t *)¬ify_en,
|
||||
ESP_GATT_WRITE_TYPE_RSP,
|
||||
ESP_GATT_AUTH_REQ_NONE);
|
||||
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_NOTIFY_EVT:
|
||||
ESP_LOGI(GATTC_TAG,"ESP_GATTC_NOTIFY_EVT\n");
|
||||
notify_event_handler(p_data);
|
||||
break;
|
||||
case ESP_GATTC_READ_CHAR_EVT:
|
||||
ESP_LOGI(GATTC_TAG,"ESP_GATTC_READ_CHAR_EVT\n");
|
||||
break;
|
||||
case ESP_GATTC_WRITE_CHAR_EVT:
|
||||
ESP_LOGI(GATTC_TAG,"ESP_GATTC_WRITE_CHAR_EVT:status = %d,handle = %d", param->write.status, param->write.handle);
|
||||
if(param->write.status != ESP_GATT_OK){
|
||||
ESP_LOGE(GATTC_TAG, "ESP_GATTC_WRITE_CHAR_EVT, error status = %d", p_data->write.status);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case ESP_GATTC_PREP_WRITE_EVT:
|
||||
break;
|
||||
case ESP_GATTC_EXEC_EVT:
|
||||
break;
|
||||
case ESP_GATTC_WRITE_DESCR_EVT:
|
||||
ESP_LOGI(GATTC_TAG,"ESP_GATTC_WRITE_DESCR_EVT: status =%d,handle = %d \n", p_data->write.status, p_data->write.handle);
|
||||
if(p_data->write.status != ESP_GATT_OK){
|
||||
ESP_LOGE(GATTC_TAG, "ESP_GATTC_WRITE_DESCR_EVT, error status = %d", p_data->write.status);
|
||||
break;
|
||||
}
|
||||
switch(cmd){
|
||||
case SPP_IDX_SPP_DATA_NTY_VAL:
|
||||
cmd = SPP_IDX_SPP_STATUS_VAL;
|
||||
xQueueSend(cmd_reg_queue, &cmd,10/portTICK_PERIOD_MS);
|
||||
break;
|
||||
case SPP_IDX_SPP_STATUS_VAL:
|
||||
#ifdef SUPPORT_HEARTBEAT
|
||||
cmd = SPP_IDX_SPP_HEARTBEAT_VAL;
|
||||
xQueueSend(cmd_reg_queue, &cmd, 10/portTICK_PERIOD_MS);
|
||||
#endif
|
||||
break;
|
||||
#ifdef SUPPORT_HEARTBEAT
|
||||
case SPP_IDX_SPP_HEARTBEAT_VAL:
|
||||
xQueueSend(cmd_heartbeat_queue, &cmd, 10/portTICK_PERIOD_MS);
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
break;
|
||||
};
|
||||
break;
|
||||
case ESP_GATTC_CFG_MTU_EVT:
|
||||
if(p_data->cfg_mtu.status != ESP_OK){
|
||||
break;
|
||||
}
|
||||
ESP_LOGI(GATTC_TAG,"+MTU:%d\n", p_data->cfg_mtu.mtu);
|
||||
spp_mtu_size = p_data->cfg_mtu.mtu;
|
||||
|
||||
db = (esp_gattc_db_elem_t *)malloc(count*sizeof(esp_gattc_db_elem_t));
|
||||
if(db == NULL){
|
||||
ESP_LOGE(GATTC_TAG,"%s:malloc db falied\n",__func__);
|
||||
break;
|
||||
}
|
||||
if(esp_ble_gattc_get_db(spp_gattc_if, spp_conn_id, spp_srv_start_handle, spp_srv_end_handle, db, &count) != ESP_GATT_OK){
|
||||
ESP_LOGE(GATTC_TAG,"%s:get db falied\n",__func__);
|
||||
break;
|
||||
}
|
||||
if(count != SPP_IDX_NB){
|
||||
ESP_LOGE(GATTC_TAG,"%s:get db count != SPP_IDX_NB, count = %d, SPP_IDX_NB = %d\n",__func__,count,SPP_IDX_NB);
|
||||
break;
|
||||
}
|
||||
for(int i = 0;i < SPP_IDX_NB;i++){
|
||||
switch((db+i)->type){
|
||||
case ESP_GATT_DB_PRIMARY_SERVICE:
|
||||
ESP_LOGI(GATTC_TAG,"attr_type = PRIMARY_SERVICE,attribute_handle=%d,start_handle=%d,end_handle=%d,properties=0x%x,uuid=0x%04x\n",\
|
||||
(db+i)->attribute_handle, (db+i)->start_handle, (db+i)->end_handle, (db+i)->properties, (db+i)->uuid.uuid.uuid16);
|
||||
break;
|
||||
case ESP_GATT_DB_SECONDARY_SERVICE:
|
||||
ESP_LOGI(GATTC_TAG,"attr_type = SECONDARY_SERVICE,attribute_handle=%d,start_handle=%d,end_handle=%d,properties=0x%x,uuid=0x%04x\n",\
|
||||
(db+i)->attribute_handle, (db+i)->start_handle, (db+i)->end_handle, (db+i)->properties, (db+i)->uuid.uuid.uuid16);
|
||||
break;
|
||||
case ESP_GATT_DB_CHARACTERISTIC:
|
||||
ESP_LOGI(GATTC_TAG,"attr_type = CHARACTERISTIC,attribute_handle=%d,start_handle=%d,end_handle=%d,properties=0x%x,uuid=0x%04x\n",\
|
||||
(db+i)->attribute_handle, (db+i)->start_handle, (db+i)->end_handle, (db+i)->properties, (db+i)->uuid.uuid.uuid16);
|
||||
break;
|
||||
case ESP_GATT_DB_DESCRIPTOR:
|
||||
ESP_LOGI(GATTC_TAG,"attr_type = DESCRIPTOR,attribute_handle=%d,start_handle=%d,end_handle=%d,properties=0x%x,uuid=0x%04x\n",\
|
||||
(db+i)->attribute_handle, (db+i)->start_handle, (db+i)->end_handle, (db+i)->properties, (db+i)->uuid.uuid.uuid16);
|
||||
break;
|
||||
case ESP_GATT_DB_INCLUDED_SERVICE:
|
||||
ESP_LOGI(GATTC_TAG,"attr_type = INCLUDED_SERVICE,attribute_handle=%d,start_handle=%d,end_handle=%d,properties=0x%x,uuid=0x%04x\n",\
|
||||
(db+i)->attribute_handle, (db+i)->start_handle, (db+i)->end_handle, (db+i)->properties, (db+i)->uuid.uuid.uuid16);
|
||||
break;
|
||||
case ESP_GATT_DB_ALL:
|
||||
ESP_LOGI(GATTC_TAG,"attr_type = ESP_GATT_DB_ALL,attribute_handle=%d,start_handle=%d,end_handle=%d,properties=0x%x,uuid=0x%04x\n",\
|
||||
(db+i)->attribute_handle, (db+i)->start_handle, (db+i)->end_handle, (db+i)->properties, (db+i)->uuid.uuid.uuid16);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
cmd = SPP_IDX_SPP_DATA_NTY_VAL;
|
||||
xQueueSend(cmd_reg_queue, &cmd, 10/portTICK_PERIOD_MS);
|
||||
break;
|
||||
case ESP_GATTC_SRVC_CHG_EVT:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void spp_client_reg_task(void* arg)
|
||||
{
|
||||
uint16_t cmd_id;
|
||||
for(;;) {
|
||||
vTaskDelay(100 / portTICK_PERIOD_MS);
|
||||
if(xQueueReceive(cmd_reg_queue, &cmd_id, portMAX_DELAY)) {
|
||||
if(db != NULL) {
|
||||
if(cmd_id == SPP_IDX_SPP_DATA_NTY_VAL){
|
||||
ESP_LOGI(GATTC_TAG,"Index = %d,UUID = 0x%04x, handle = %d \n", cmd_id, (db+SPP_IDX_SPP_DATA_NTY_VAL)->uuid.uuid.uuid16, (db+SPP_IDX_SPP_DATA_NTY_VAL)->attribute_handle);
|
||||
esp_ble_gattc_register_for_notify(spp_gattc_if, gl_profile_tab[PROFILE_APP_ID].remote_bda, (db+SPP_IDX_SPP_DATA_NTY_VAL)->attribute_handle);
|
||||
}else if(cmd_id == SPP_IDX_SPP_STATUS_VAL){
|
||||
ESP_LOGI(GATTC_TAG,"Index = %d,UUID = 0x%04x, handle = %d \n", cmd_id, (db+SPP_IDX_SPP_STATUS_VAL)->uuid.uuid.uuid16, (db+SPP_IDX_SPP_STATUS_VAL)->attribute_handle);
|
||||
esp_ble_gattc_register_for_notify(spp_gattc_if, gl_profile_tab[PROFILE_APP_ID].remote_bda, (db+SPP_IDX_SPP_STATUS_VAL)->attribute_handle);
|
||||
}
|
||||
#ifdef SUPPORT_HEARTBEAT
|
||||
else if(cmd_id == SPP_IDX_SPP_HEARTBEAT_VAL){
|
||||
ESP_LOGI(GATTC_TAG,"Index = %d,UUID = 0x%04x, handle = %d \n", cmd_id, (db+SPP_IDX_SPP_HEARTBEAT_VAL)->uuid.uuid.uuid16, (db+SPP_IDX_SPP_HEARTBEAT_VAL)->attribute_handle);
|
||||
esp_ble_gattc_register_for_notify(spp_gattc_if, gl_profile_tab[PROFILE_APP_ID].remote_bda, (db+SPP_IDX_SPP_HEARTBEAT_VAL)->attribute_handle);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef SUPPORT_HEARTBEAT
|
||||
void spp_heart_beat_task(void * arg)
|
||||
{
|
||||
uint16_t cmd_id;
|
||||
|
||||
for(;;) {
|
||||
vTaskDelay(50 / portTICK_PERIOD_MS);
|
||||
if(xQueueReceive(cmd_heartbeat_queue, &cmd_id, portMAX_DELAY)) {
|
||||
while(1){
|
||||
if((is_connect == true) && (db != NULL) && ((db+SPP_IDX_SPP_HEARTBEAT_VAL)->properties & (ESP_GATT_CHAR_PROP_BIT_WRITE_NR | ESP_GATT_CHAR_PROP_BIT_WRITE))){
|
||||
esp_ble_gattc_write_char( spp_gattc_if,
|
||||
spp_conn_id,
|
||||
(db+SPP_IDX_SPP_HEARTBEAT_VAL)->attribute_handle,
|
||||
sizeof(heartbeat_s),
|
||||
(uint8_t *)heartbeat_s,
|
||||
ESP_GATT_WRITE_TYPE_RSP,
|
||||
ESP_GATT_AUTH_REQ_NONE);
|
||||
vTaskDelay(5000 / portTICK_PERIOD_MS);
|
||||
}else{
|
||||
ESP_LOGI(GATTC_TAG,"disconnect\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void ble_client_appRegister(void)
|
||||
{
|
||||
esp_err_t status;
|
||||
char err_msg[20];
|
||||
|
||||
ESP_LOGI(GATTC_TAG, "register callback");
|
||||
|
||||
//register the scan callback function to the gap module
|
||||
if ((status = esp_ble_gap_register_callback(esp_gap_cb)) != ESP_OK) {
|
||||
ESP_LOGE(GATTC_TAG, "gap register error: %s", esp_err_to_name_r(status, err_msg, sizeof(err_msg)));
|
||||
return;
|
||||
}
|
||||
//register the callback function to the gattc module
|
||||
if ((status = esp_ble_gattc_register_callback(esp_gattc_cb)) != ESP_OK) {
|
||||
ESP_LOGE(GATTC_TAG, "gattc register error: %s", esp_err_to_name_r(status, err_msg, sizeof(err_msg)));
|
||||
return;
|
||||
}
|
||||
esp_ble_gattc_app_register(PROFILE_APP_ID);
|
||||
|
||||
esp_err_t local_mtu_ret = esp_ble_gatt_set_local_mtu(200);
|
||||
if (local_mtu_ret){
|
||||
ESP_LOGE(GATTC_TAG, "set local MTU failed: %s", esp_err_to_name_r(local_mtu_ret, err_msg, sizeof(err_msg)));
|
||||
}
|
||||
|
||||
cmd_reg_queue = xQueueCreate(10, sizeof(uint32_t));
|
||||
xTaskCreate(spp_client_reg_task, "spp_client_reg_task", 2048, NULL, 10, NULL);
|
||||
|
||||
#ifdef SUPPORT_HEARTBEAT
|
||||
cmd_heartbeat_queue = xQueueCreate(10, sizeof(uint32_t));
|
||||
xTaskCreate(spp_heart_beat_task, "spp_heart_beat_task", 2048, NULL, 10, NULL);
|
||||
#endif
|
||||
}
|
||||
|
||||
void uart_task(void *pvParameters)
|
||||
{
|
||||
uart_event_t event;
|
||||
for (;;) {
|
||||
//Waiting for UART event.
|
||||
if (xQueueReceive(spp_uart_queue, (void * )&event, (portTickType)portMAX_DELAY)) {
|
||||
switch (event.type) {
|
||||
//Event of UART receving data
|
||||
case UART_DATA:
|
||||
if (event.size && (is_connect == true) && (db != NULL) && ((db+SPP_IDX_SPP_DATA_RECV_VAL)->properties & (ESP_GATT_CHAR_PROP_BIT_WRITE_NR | ESP_GATT_CHAR_PROP_BIT_WRITE))) {
|
||||
uint8_t * temp = NULL;
|
||||
temp = (uint8_t *)malloc(sizeof(uint8_t)*event.size);
|
||||
if(temp == NULL){
|
||||
ESP_LOGE(GATTC_TAG, "malloc failed,%s L#%d\n", __func__, __LINE__);
|
||||
break;
|
||||
}
|
||||
memset(temp, 0x0, event.size);
|
||||
uart_read_bytes(UART_NUM_0,temp,event.size,portMAX_DELAY);
|
||||
esp_ble_gattc_write_char( spp_gattc_if,
|
||||
spp_conn_id,
|
||||
(db+SPP_IDX_SPP_DATA_RECV_VAL)->attribute_handle,
|
||||
event.size,
|
||||
temp,
|
||||
ESP_GATT_WRITE_TYPE_RSP,
|
||||
ESP_GATT_AUTH_REQ_NONE);
|
||||
free(temp);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
static void spp_uart_init(void)
|
||||
{
|
||||
uart_config_t uart_config = {
|
||||
.baud_rate = 115200,
|
||||
.data_bits = UART_DATA_8_BITS,
|
||||
.parity = UART_PARITY_DISABLE,
|
||||
.stop_bits = UART_STOP_BITS_1,
|
||||
.flow_ctrl = UART_HW_FLOWCTRL_RTS,
|
||||
.rx_flow_ctrl_thresh = 122,
|
||||
.source_clk = UART_SCLK_APB,
|
||||
};
|
||||
|
||||
//Install UART driver, and get the queue.
|
||||
uart_driver_install(UART_NUM_0, 4096, 8192, 10, &spp_uart_queue, 0);
|
||||
//Set UART parameters
|
||||
uart_param_config(UART_NUM_0, &uart_config);
|
||||
//Set UART pins
|
||||
uart_set_pin(UART_NUM_0, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
|
||||
xTaskCreate(uart_task, "uTask", 2048, (void*)UART_NUM_0, 8, NULL);
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
esp_err_t 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();
|
||||
|
||||
nvs_flash_init();
|
||||
ret = esp_bt_controller_init(&bt_cfg);
|
||||
if (ret) {
|
||||
ESP_LOGE(GATTC_TAG, "%s enable controller failed: %s\n", __func__, esp_err_to_name(ret));
|
||||
return;
|
||||
}
|
||||
|
||||
ret = esp_bt_controller_enable(ESP_BT_MODE_BLE);
|
||||
if (ret) {
|
||||
ESP_LOGE(GATTC_TAG, "%s enable controller failed: %s\n", __func__, esp_err_to_name(ret));
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGI(GATTC_TAG, "%s init bluetooth\n", __func__);
|
||||
ret = esp_bluedroid_init();
|
||||
if (ret) {
|
||||
ESP_LOGE(GATTC_TAG, "%s init bluetooth failed: %s\n", __func__, esp_err_to_name(ret));
|
||||
return;
|
||||
}
|
||||
ret = esp_bluedroid_enable();
|
||||
if (ret) {
|
||||
ESP_LOGE(GATTC_TAG, "%s enable bluetooth failed: %s\n", __func__, esp_err_to_name(ret));
|
||||
return;
|
||||
}
|
||||
|
||||
ble_client_appRegister();
|
||||
spp_uart_init();
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
# Override some defaults so BT stack is enabled
|
||||
# and WiFi disabled by default in this example
|
||||
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
|
||||
@@ -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_spp_server_demo)
|
||||
@@ -0,0 +1,11 @@
|
||||
#
|
||||
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
|
||||
# project subdirectory.
|
||||
#
|
||||
|
||||
PROJECT_NAME := ble_spp_server_demo
|
||||
|
||||
COMPONENT_ADD_INCLUDEDIRS := components/include
|
||||
|
||||
include $(IDF_PATH)/make/project.mk
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
| Supported Targets | ESP32 |
|
||||
| ----------------- | ----- |
|
||||
|
||||
## ESP-IDF GATT SERVER SPP demo
|
||||
|
||||
For description of this application please refer to [ESP-IDF GATT CLIENT SPP demo](../ble_spp_client/README.md)
|
||||
@@ -0,0 +1,2 @@
|
||||
idf_component_register(SRCS "ble_spp_server_demo.c"
|
||||
INCLUDE_DIRS ".")
|
||||
@@ -0,0 +1,701 @@
|
||||
/*
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/event_groups.h"
|
||||
#include "esp_system.h"
|
||||
#include "esp_log.h"
|
||||
#include "nvs_flash.h"
|
||||
#include "esp_bt.h"
|
||||
#include "driver/uart.h"
|
||||
#include "string.h"
|
||||
|
||||
#include "esp_gap_ble_api.h"
|
||||
#include "esp_gatts_api.h"
|
||||
#include "esp_bt_defs.h"
|
||||
#include "esp_bt_main.h"
|
||||
#include "ble_spp_server_demo.h"
|
||||
|
||||
#define GATTS_TABLE_TAG "GATTS_SPP_DEMO"
|
||||
|
||||
#define SPP_PROFILE_NUM 1
|
||||
#define SPP_PROFILE_APP_IDX 0
|
||||
#define ESP_SPP_APP_ID 0x56
|
||||
#define SAMPLE_DEVICE_NAME "ESP_SPP_SERVER" //The Device Name Characteristics in GAP
|
||||
#define SPP_SVC_INST_ID 0
|
||||
|
||||
/// SPP Service
|
||||
static const uint16_t spp_service_uuid = 0xABF0;
|
||||
/// Characteristic UUID
|
||||
#define ESP_GATT_UUID_SPP_DATA_RECEIVE 0xABF1
|
||||
#define ESP_GATT_UUID_SPP_DATA_NOTIFY 0xABF2
|
||||
#define ESP_GATT_UUID_SPP_COMMAND_RECEIVE 0xABF3
|
||||
#define ESP_GATT_UUID_SPP_COMMAND_NOTIFY 0xABF4
|
||||
|
||||
#ifdef SUPPORT_HEARTBEAT
|
||||
#define ESP_GATT_UUID_SPP_HEARTBEAT 0xABF5
|
||||
#endif
|
||||
|
||||
static const uint8_t spp_adv_data[23] = {
|
||||
/* Flags */
|
||||
0x02,0x01,0x06,
|
||||
/* Complete List of 16-bit Service Class UUIDs */
|
||||
0x03,0x03,0xF0,0xAB,
|
||||
/* Complete Local Name in advertising */
|
||||
0x0F,0x09, 'E', 'S', 'P', '_', 'S', 'P', 'P', '_', 'S', 'E', 'R','V', 'E', 'R'
|
||||
};
|
||||
|
||||
static uint16_t spp_mtu_size = 23;
|
||||
static uint16_t spp_conn_id = 0xffff;
|
||||
static esp_gatt_if_t spp_gatts_if = 0xff;
|
||||
QueueHandle_t spp_uart_queue = NULL;
|
||||
static xQueueHandle cmd_cmd_queue = NULL;
|
||||
|
||||
#ifdef SUPPORT_HEARTBEAT
|
||||
static xQueueHandle cmd_heartbeat_queue = NULL;
|
||||
static uint8_t heartbeat_s[9] = {'E','s','p','r','e','s','s','i','f'};
|
||||
static bool enable_heart_ntf = false;
|
||||
static uint8_t heartbeat_count_num = 0;
|
||||
#endif
|
||||
|
||||
static bool enable_data_ntf = false;
|
||||
static bool is_connected = false;
|
||||
static esp_bd_addr_t spp_remote_bda = {0x0,};
|
||||
|
||||
static uint16_t spp_handle_table[SPP_IDX_NB];
|
||||
|
||||
static esp_ble_adv_params_t spp_adv_params = {
|
||||
.adv_int_min = 0x20,
|
||||
.adv_int_max = 0x40,
|
||||
.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 gatts_profile_inst {
|
||||
esp_gatts_cb_t gatts_cb;
|
||||
uint16_t gatts_if;
|
||||
uint16_t app_id;
|
||||
uint16_t conn_id;
|
||||
uint16_t service_handle;
|
||||
esp_gatt_srvc_id_t service_id;
|
||||
uint16_t char_handle;
|
||||
esp_bt_uuid_t char_uuid;
|
||||
esp_gatt_perm_t perm;
|
||||
esp_gatt_char_prop_t property;
|
||||
uint16_t descr_handle;
|
||||
esp_bt_uuid_t descr_uuid;
|
||||
};
|
||||
|
||||
typedef struct spp_receive_data_node{
|
||||
int32_t len;
|
||||
uint8_t * node_buff;
|
||||
struct spp_receive_data_node * next_node;
|
||||
}spp_receive_data_node_t;
|
||||
|
||||
static spp_receive_data_node_t * temp_spp_recv_data_node_p1 = NULL;
|
||||
static spp_receive_data_node_t * temp_spp_recv_data_node_p2 = NULL;
|
||||
|
||||
typedef struct spp_receive_data_buff{
|
||||
int32_t node_num;
|
||||
int32_t buff_size;
|
||||
spp_receive_data_node_t * first_node;
|
||||
}spp_receive_data_buff_t;
|
||||
|
||||
static spp_receive_data_buff_t SppRecvDataBuff = {
|
||||
.node_num = 0,
|
||||
.buff_size = 0,
|
||||
.first_node = NULL
|
||||
};
|
||||
|
||||
static void gatts_profile_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param);
|
||||
|
||||
/* One gatt-based profile one app_id and one gatts_if, this array will store the gatts_if returned by ESP_GATTS_REG_EVT */
|
||||
static struct gatts_profile_inst spp_profile_tab[SPP_PROFILE_NUM] = {
|
||||
[SPP_PROFILE_APP_IDX] = {
|
||||
.gatts_cb = gatts_profile_event_handler,
|
||||
.gatts_if = ESP_GATT_IF_NONE, /* Not get the gatt_if, so initial is ESP_GATT_IF_NONE */
|
||||
},
|
||||
};
|
||||
|
||||
/*
|
||||
* SPP PROFILE ATTRIBUTES
|
||||
****************************************************************************************
|
||||
*/
|
||||
|
||||
#define CHAR_DECLARATION_SIZE (sizeof(uint8_t))
|
||||
static const uint16_t primary_service_uuid = ESP_GATT_UUID_PRI_SERVICE;
|
||||
static const uint16_t character_declaration_uuid = ESP_GATT_UUID_CHAR_DECLARE;
|
||||
static const uint16_t character_client_config_uuid = ESP_GATT_UUID_CHAR_CLIENT_CONFIG;
|
||||
|
||||
static const uint8_t char_prop_read_notify = ESP_GATT_CHAR_PROP_BIT_READ|ESP_GATT_CHAR_PROP_BIT_NOTIFY;
|
||||
static const uint8_t char_prop_read_write = ESP_GATT_CHAR_PROP_BIT_WRITE_NR|ESP_GATT_CHAR_PROP_BIT_READ;
|
||||
|
||||
#ifdef SUPPORT_HEARTBEAT
|
||||
static const uint8_t char_prop_read_write_notify = ESP_GATT_CHAR_PROP_BIT_READ|ESP_GATT_CHAR_PROP_BIT_WRITE_NR|ESP_GATT_CHAR_PROP_BIT_NOTIFY;
|
||||
#endif
|
||||
|
||||
///SPP Service - data receive characteristic, read&write without response
|
||||
static const uint16_t spp_data_receive_uuid = ESP_GATT_UUID_SPP_DATA_RECEIVE;
|
||||
static const uint8_t spp_data_receive_val[20] = {0x00};
|
||||
|
||||
///SPP Service - data notify characteristic, notify&read
|
||||
static const uint16_t spp_data_notify_uuid = ESP_GATT_UUID_SPP_DATA_NOTIFY;
|
||||
static const uint8_t spp_data_notify_val[20] = {0x00};
|
||||
static const uint8_t spp_data_notify_ccc[2] = {0x00, 0x00};
|
||||
|
||||
///SPP Service - command characteristic, read&write without response
|
||||
static const uint16_t spp_command_uuid = ESP_GATT_UUID_SPP_COMMAND_RECEIVE;
|
||||
static const uint8_t spp_command_val[10] = {0x00};
|
||||
|
||||
///SPP Service - status characteristic, notify&read
|
||||
static const uint16_t spp_status_uuid = ESP_GATT_UUID_SPP_COMMAND_NOTIFY;
|
||||
static const uint8_t spp_status_val[10] = {0x00};
|
||||
static const uint8_t spp_status_ccc[2] = {0x00, 0x00};
|
||||
|
||||
#ifdef SUPPORT_HEARTBEAT
|
||||
///SPP Server - Heart beat characteristic, notify&write&read
|
||||
static const uint16_t spp_heart_beat_uuid = ESP_GATT_UUID_SPP_HEARTBEAT;
|
||||
static const uint8_t spp_heart_beat_val[2] = {0x00, 0x00};
|
||||
static const uint8_t spp_heart_beat_ccc[2] = {0x00, 0x00};
|
||||
#endif
|
||||
|
||||
///Full HRS Database Description - Used to add attributes into the database
|
||||
static const esp_gatts_attr_db_t spp_gatt_db[SPP_IDX_NB] =
|
||||
{
|
||||
//SPP - Service Declaration
|
||||
[SPP_IDX_SVC] =
|
||||
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&primary_service_uuid, ESP_GATT_PERM_READ,
|
||||
sizeof(spp_service_uuid), sizeof(spp_service_uuid), (uint8_t *)&spp_service_uuid}},
|
||||
|
||||
//SPP - data receive characteristic Declaration
|
||||
[SPP_IDX_SPP_DATA_RECV_CHAR] =
|
||||
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_declaration_uuid, ESP_GATT_PERM_READ,
|
||||
CHAR_DECLARATION_SIZE,CHAR_DECLARATION_SIZE, (uint8_t *)&char_prop_read_write}},
|
||||
|
||||
//SPP - data receive characteristic Value
|
||||
[SPP_IDX_SPP_DATA_RECV_VAL] =
|
||||
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&spp_data_receive_uuid, ESP_GATT_PERM_READ|ESP_GATT_PERM_WRITE,
|
||||
SPP_DATA_MAX_LEN,sizeof(spp_data_receive_val), (uint8_t *)spp_data_receive_val}},
|
||||
|
||||
//SPP - data notify characteristic Declaration
|
||||
[SPP_IDX_SPP_DATA_NOTIFY_CHAR] =
|
||||
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_declaration_uuid, ESP_GATT_PERM_READ,
|
||||
CHAR_DECLARATION_SIZE,CHAR_DECLARATION_SIZE, (uint8_t *)&char_prop_read_notify}},
|
||||
|
||||
//SPP - data notify characteristic Value
|
||||
[SPP_IDX_SPP_DATA_NTY_VAL] =
|
||||
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&spp_data_notify_uuid, ESP_GATT_PERM_READ,
|
||||
SPP_DATA_MAX_LEN, sizeof(spp_data_notify_val), (uint8_t *)spp_data_notify_val}},
|
||||
|
||||
//SPP - data notify characteristic - Client Characteristic Configuration Descriptor
|
||||
[SPP_IDX_SPP_DATA_NTF_CFG] =
|
||||
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_client_config_uuid, ESP_GATT_PERM_READ|ESP_GATT_PERM_WRITE,
|
||||
sizeof(uint16_t),sizeof(spp_data_notify_ccc), (uint8_t *)spp_data_notify_ccc}},
|
||||
|
||||
//SPP - command characteristic Declaration
|
||||
[SPP_IDX_SPP_COMMAND_CHAR] =
|
||||
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_declaration_uuid, ESP_GATT_PERM_READ,
|
||||
CHAR_DECLARATION_SIZE,CHAR_DECLARATION_SIZE, (uint8_t *)&char_prop_read_write}},
|
||||
|
||||
//SPP - command characteristic Value
|
||||
[SPP_IDX_SPP_COMMAND_VAL] =
|
||||
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&spp_command_uuid, ESP_GATT_PERM_READ|ESP_GATT_PERM_WRITE,
|
||||
SPP_CMD_MAX_LEN,sizeof(spp_command_val), (uint8_t *)spp_command_val}},
|
||||
|
||||
//SPP - status characteristic Declaration
|
||||
[SPP_IDX_SPP_STATUS_CHAR] =
|
||||
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_declaration_uuid, ESP_GATT_PERM_READ,
|
||||
CHAR_DECLARATION_SIZE,CHAR_DECLARATION_SIZE, (uint8_t *)&char_prop_read_notify}},
|
||||
|
||||
//SPP - status characteristic Value
|
||||
[SPP_IDX_SPP_STATUS_VAL] =
|
||||
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&spp_status_uuid, ESP_GATT_PERM_READ,
|
||||
SPP_STATUS_MAX_LEN,sizeof(spp_status_val), (uint8_t *)spp_status_val}},
|
||||
|
||||
//SPP - status characteristic - Client Characteristic Configuration Descriptor
|
||||
[SPP_IDX_SPP_STATUS_CFG] =
|
||||
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_client_config_uuid, ESP_GATT_PERM_READ|ESP_GATT_PERM_WRITE,
|
||||
sizeof(uint16_t),sizeof(spp_status_ccc), (uint8_t *)spp_status_ccc}},
|
||||
|
||||
#ifdef SUPPORT_HEARTBEAT
|
||||
//SPP - Heart beat characteristic Declaration
|
||||
[SPP_IDX_SPP_HEARTBEAT_CHAR] =
|
||||
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_declaration_uuid, ESP_GATT_PERM_READ,
|
||||
CHAR_DECLARATION_SIZE,CHAR_DECLARATION_SIZE, (uint8_t *)&char_prop_read_write_notify}},
|
||||
|
||||
//SPP - Heart beat characteristic Value
|
||||
[SPP_IDX_SPP_HEARTBEAT_VAL] =
|
||||
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&spp_heart_beat_uuid, ESP_GATT_PERM_READ|ESP_GATT_PERM_WRITE,
|
||||
sizeof(spp_heart_beat_val), sizeof(spp_heart_beat_val), (uint8_t *)spp_heart_beat_val}},
|
||||
|
||||
//SPP - Heart beat characteristic - Client Characteristic Configuration Descriptor
|
||||
[SPP_IDX_SPP_HEARTBEAT_CFG] =
|
||||
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_client_config_uuid, ESP_GATT_PERM_READ|ESP_GATT_PERM_WRITE,
|
||||
sizeof(uint16_t),sizeof(spp_data_notify_ccc), (uint8_t *)spp_heart_beat_ccc}},
|
||||
#endif
|
||||
};
|
||||
|
||||
static uint8_t find_char_and_desr_index(uint16_t handle)
|
||||
{
|
||||
uint8_t error = 0xff;
|
||||
|
||||
for(int i = 0; i < SPP_IDX_NB ; i++){
|
||||
if(handle == spp_handle_table[i]){
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
static bool store_wr_buffer(esp_ble_gatts_cb_param_t *p_data)
|
||||
{
|
||||
temp_spp_recv_data_node_p1 = (spp_receive_data_node_t *)malloc(sizeof(spp_receive_data_node_t));
|
||||
|
||||
if(temp_spp_recv_data_node_p1 == NULL){
|
||||
ESP_LOGI(GATTS_TABLE_TAG, "malloc error %s %d\n", __func__, __LINE__);
|
||||
return false;
|
||||
}
|
||||
if(temp_spp_recv_data_node_p2 != NULL){
|
||||
temp_spp_recv_data_node_p2->next_node = temp_spp_recv_data_node_p1;
|
||||
}
|
||||
temp_spp_recv_data_node_p1->len = p_data->write.len;
|
||||
SppRecvDataBuff.buff_size += p_data->write.len;
|
||||
temp_spp_recv_data_node_p1->next_node = NULL;
|
||||
temp_spp_recv_data_node_p1->node_buff = (uint8_t *)malloc(p_data->write.len);
|
||||
temp_spp_recv_data_node_p2 = temp_spp_recv_data_node_p1;
|
||||
memcpy(temp_spp_recv_data_node_p1->node_buff,p_data->write.value,p_data->write.len);
|
||||
if(SppRecvDataBuff.node_num == 0){
|
||||
SppRecvDataBuff.first_node = temp_spp_recv_data_node_p1;
|
||||
SppRecvDataBuff.node_num++;
|
||||
}else{
|
||||
SppRecvDataBuff.node_num++;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void free_write_buffer(void)
|
||||
{
|
||||
temp_spp_recv_data_node_p1 = SppRecvDataBuff.first_node;
|
||||
|
||||
while(temp_spp_recv_data_node_p1 != NULL){
|
||||
temp_spp_recv_data_node_p2 = temp_spp_recv_data_node_p1->next_node;
|
||||
free(temp_spp_recv_data_node_p1->node_buff);
|
||||
free(temp_spp_recv_data_node_p1);
|
||||
temp_spp_recv_data_node_p1 = temp_spp_recv_data_node_p2;
|
||||
}
|
||||
|
||||
SppRecvDataBuff.node_num = 0;
|
||||
SppRecvDataBuff.buff_size = 0;
|
||||
SppRecvDataBuff.first_node = NULL;
|
||||
}
|
||||
|
||||
static void print_write_buffer(void)
|
||||
{
|
||||
temp_spp_recv_data_node_p1 = SppRecvDataBuff.first_node;
|
||||
|
||||
while(temp_spp_recv_data_node_p1 != NULL){
|
||||
uart_write_bytes(UART_NUM_0, (char *)(temp_spp_recv_data_node_p1->node_buff), temp_spp_recv_data_node_p1->len);
|
||||
temp_spp_recv_data_node_p1 = temp_spp_recv_data_node_p1->next_node;
|
||||
}
|
||||
}
|
||||
|
||||
void uart_task(void *pvParameters)
|
||||
{
|
||||
uart_event_t event;
|
||||
uint8_t total_num = 0;
|
||||
uint8_t current_num = 0;
|
||||
|
||||
for (;;) {
|
||||
//Waiting for UART event.
|
||||
if (xQueueReceive(spp_uart_queue, (void * )&event, (portTickType)portMAX_DELAY)) {
|
||||
switch (event.type) {
|
||||
//Event of UART receving data
|
||||
case UART_DATA:
|
||||
if ((event.size)&&(is_connected)) {
|
||||
uint8_t * temp = NULL;
|
||||
uint8_t * ntf_value_p = NULL;
|
||||
#ifdef SUPPORT_HEARTBEAT
|
||||
if(!enable_heart_ntf){
|
||||
ESP_LOGE(GATTS_TABLE_TAG, "%s do not enable heartbeat Notify\n", __func__);
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
if(!enable_data_ntf){
|
||||
ESP_LOGE(GATTS_TABLE_TAG, "%s do not enable data Notify\n", __func__);
|
||||
break;
|
||||
}
|
||||
temp = (uint8_t *)malloc(sizeof(uint8_t)*event.size);
|
||||
if(temp == NULL){
|
||||
ESP_LOGE(GATTS_TABLE_TAG, "%s malloc.1 failed\n", __func__);
|
||||
break;
|
||||
}
|
||||
memset(temp,0x0,event.size);
|
||||
uart_read_bytes(UART_NUM_0,temp,event.size,portMAX_DELAY);
|
||||
if(event.size <= (spp_mtu_size - 3)){
|
||||
esp_ble_gatts_send_indicate(spp_gatts_if, spp_conn_id, spp_handle_table[SPP_IDX_SPP_DATA_NTY_VAL],event.size, temp, false);
|
||||
}else if(event.size > (spp_mtu_size - 3)){
|
||||
if((event.size%(spp_mtu_size - 7)) == 0){
|
||||
total_num = event.size/(spp_mtu_size - 7);
|
||||
}else{
|
||||
total_num = event.size/(spp_mtu_size - 7) + 1;
|
||||
}
|
||||
current_num = 1;
|
||||
ntf_value_p = (uint8_t *)malloc((spp_mtu_size-3)*sizeof(uint8_t));
|
||||
if(ntf_value_p == NULL){
|
||||
ESP_LOGE(GATTS_TABLE_TAG, "%s malloc.2 failed\n", __func__);
|
||||
free(temp);
|
||||
break;
|
||||
}
|
||||
while(current_num <= total_num){
|
||||
if(current_num < total_num){
|
||||
ntf_value_p[0] = '#';
|
||||
ntf_value_p[1] = '#';
|
||||
ntf_value_p[2] = total_num;
|
||||
ntf_value_p[3] = current_num;
|
||||
memcpy(ntf_value_p + 4,temp + (current_num - 1)*(spp_mtu_size-7),(spp_mtu_size-7));
|
||||
esp_ble_gatts_send_indicate(spp_gatts_if, spp_conn_id, spp_handle_table[SPP_IDX_SPP_DATA_NTY_VAL],(spp_mtu_size-3), ntf_value_p, false);
|
||||
}else if(current_num == total_num){
|
||||
ntf_value_p[0] = '#';
|
||||
ntf_value_p[1] = '#';
|
||||
ntf_value_p[2] = total_num;
|
||||
ntf_value_p[3] = current_num;
|
||||
memcpy(ntf_value_p + 4,temp + (current_num - 1)*(spp_mtu_size-7),(event.size - (current_num - 1)*(spp_mtu_size - 7)));
|
||||
esp_ble_gatts_send_indicate(spp_gatts_if, spp_conn_id, spp_handle_table[SPP_IDX_SPP_DATA_NTY_VAL],(event.size - (current_num - 1)*(spp_mtu_size - 7) + 4), ntf_value_p, false);
|
||||
}
|
||||
vTaskDelay(20 / portTICK_PERIOD_MS);
|
||||
current_num++;
|
||||
}
|
||||
free(ntf_value_p);
|
||||
}
|
||||
free(temp);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
static void spp_uart_init(void)
|
||||
{
|
||||
uart_config_t uart_config = {
|
||||
.baud_rate = 115200,
|
||||
.data_bits = UART_DATA_8_BITS,
|
||||
.parity = UART_PARITY_DISABLE,
|
||||
.stop_bits = UART_STOP_BITS_1,
|
||||
.flow_ctrl = UART_HW_FLOWCTRL_RTS,
|
||||
.rx_flow_ctrl_thresh = 122,
|
||||
.source_clk = UART_SCLK_APB,
|
||||
};
|
||||
|
||||
//Install UART driver, and get the queue.
|
||||
uart_driver_install(UART_NUM_0, 4096, 8192, 10,&spp_uart_queue,0);
|
||||
//Set UART parameters
|
||||
uart_param_config(UART_NUM_0, &uart_config);
|
||||
//Set UART pins
|
||||
uart_set_pin(UART_NUM_0, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
|
||||
xTaskCreate(uart_task, "uTask", 2048, (void*)UART_NUM_0, 8, NULL);
|
||||
}
|
||||
|
||||
#ifdef SUPPORT_HEARTBEAT
|
||||
void spp_heartbeat_task(void * arg)
|
||||
{
|
||||
uint16_t cmd_id;
|
||||
|
||||
for(;;) {
|
||||
vTaskDelay(50 / portTICK_PERIOD_MS);
|
||||
if(xQueueReceive(cmd_heartbeat_queue, &cmd_id, portMAX_DELAY)) {
|
||||
while(1){
|
||||
heartbeat_count_num++;
|
||||
vTaskDelay(5000/ portTICK_PERIOD_MS);
|
||||
if((heartbeat_count_num >3)&&(is_connected)){
|
||||
esp_ble_gap_disconnect(spp_remote_bda);
|
||||
}
|
||||
if(is_connected && enable_heart_ntf){
|
||||
esp_ble_gatts_send_indicate(spp_gatts_if, spp_conn_id, spp_handle_table[SPP_IDX_SPP_HEARTBEAT_VAL],sizeof(heartbeat_s), heartbeat_s, false);
|
||||
}else if(!is_connected){
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
#endif
|
||||
|
||||
void spp_cmd_task(void * arg)
|
||||
{
|
||||
uint8_t * cmd_id;
|
||||
|
||||
for(;;){
|
||||
vTaskDelay(50 / portTICK_PERIOD_MS);
|
||||
if(xQueueReceive(cmd_cmd_queue, &cmd_id, portMAX_DELAY)) {
|
||||
esp_log_buffer_char(GATTS_TABLE_TAG,(char *)(cmd_id),strlen((char *)cmd_id));
|
||||
free(cmd_id);
|
||||
}
|
||||
}
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
static void spp_task_init(void)
|
||||
{
|
||||
spp_uart_init();
|
||||
|
||||
#ifdef SUPPORT_HEARTBEAT
|
||||
cmd_heartbeat_queue = xQueueCreate(10, sizeof(uint32_t));
|
||||
xTaskCreate(spp_heartbeat_task, "spp_heartbeat_task", 2048, NULL, 10, NULL);
|
||||
#endif
|
||||
|
||||
cmd_cmd_queue = xQueueCreate(10, sizeof(uint32_t));
|
||||
xTaskCreate(spp_cmd_task, "spp_cmd_task", 2048, NULL, 10, NULL);
|
||||
}
|
||||
|
||||
static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param)
|
||||
{
|
||||
esp_err_t err;
|
||||
ESP_LOGE(GATTS_TABLE_TAG, "GAP_EVT, event %d\n", event);
|
||||
|
||||
switch (event) {
|
||||
case ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT:
|
||||
esp_ble_gap_start_advertising(&spp_adv_params);
|
||||
break;
|
||||
case ESP_GAP_BLE_ADV_START_COMPLETE_EVT:
|
||||
//advertising start complete event to indicate advertising start successfully or failed
|
||||
if((err = param->adv_start_cmpl.status) != ESP_BT_STATUS_SUCCESS) {
|
||||
ESP_LOGE(GATTS_TABLE_TAG, "Advertising start failed: %s\n", esp_err_to_name(err));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void gatts_profile_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param)
|
||||
{
|
||||
esp_ble_gatts_cb_param_t *p_data = (esp_ble_gatts_cb_param_t *) param;
|
||||
uint8_t res = 0xff;
|
||||
|
||||
ESP_LOGI(GATTS_TABLE_TAG, "event = %x\n",event);
|
||||
switch (event) {
|
||||
case ESP_GATTS_REG_EVT:
|
||||
ESP_LOGI(GATTS_TABLE_TAG, "%s %d\n", __func__, __LINE__);
|
||||
esp_ble_gap_set_device_name(SAMPLE_DEVICE_NAME);
|
||||
|
||||
ESP_LOGI(GATTS_TABLE_TAG, "%s %d\n", __func__, __LINE__);
|
||||
esp_ble_gap_config_adv_data_raw((uint8_t *)spp_adv_data, sizeof(spp_adv_data));
|
||||
|
||||
ESP_LOGI(GATTS_TABLE_TAG, "%s %d\n", __func__, __LINE__);
|
||||
esp_ble_gatts_create_attr_tab(spp_gatt_db, gatts_if, SPP_IDX_NB, SPP_SVC_INST_ID);
|
||||
break;
|
||||
case ESP_GATTS_READ_EVT:
|
||||
res = find_char_and_desr_index(p_data->read.handle);
|
||||
if(res == SPP_IDX_SPP_STATUS_VAL){
|
||||
//TODO:client read the status characteristic
|
||||
}
|
||||
break;
|
||||
case ESP_GATTS_WRITE_EVT: {
|
||||
res = find_char_and_desr_index(p_data->write.handle);
|
||||
if(p_data->write.is_prep == false){
|
||||
ESP_LOGI(GATTS_TABLE_TAG, "ESP_GATTS_WRITE_EVT : handle = %d\n", res);
|
||||
if(res == SPP_IDX_SPP_COMMAND_VAL){
|
||||
uint8_t * spp_cmd_buff = NULL;
|
||||
spp_cmd_buff = (uint8_t *)malloc((spp_mtu_size - 3) * sizeof(uint8_t));
|
||||
if(spp_cmd_buff == NULL){
|
||||
ESP_LOGE(GATTS_TABLE_TAG, "%s malloc failed\n", __func__);
|
||||
break;
|
||||
}
|
||||
memset(spp_cmd_buff,0x0,(spp_mtu_size - 3));
|
||||
memcpy(spp_cmd_buff,p_data->write.value,p_data->write.len);
|
||||
xQueueSend(cmd_cmd_queue,&spp_cmd_buff,10/portTICK_PERIOD_MS);
|
||||
}else if(res == SPP_IDX_SPP_DATA_NTF_CFG){
|
||||
if((p_data->write.len == 2)&&(p_data->write.value[0] == 0x01)&&(p_data->write.value[1] == 0x00)){
|
||||
enable_data_ntf = true;
|
||||
}else if((p_data->write.len == 2)&&(p_data->write.value[0] == 0x00)&&(p_data->write.value[1] == 0x00)){
|
||||
enable_data_ntf = false;
|
||||
}
|
||||
}
|
||||
#ifdef SUPPORT_HEARTBEAT
|
||||
else if(res == SPP_IDX_SPP_HEARTBEAT_CFG){
|
||||
if((p_data->write.len == 2)&&(p_data->write.value[0] == 0x01)&&(p_data->write.value[1] == 0x00)){
|
||||
enable_heart_ntf = true;
|
||||
}else if((p_data->write.len == 2)&&(p_data->write.value[0] == 0x00)&&(p_data->write.value[1] == 0x00)){
|
||||
enable_heart_ntf = false;
|
||||
}
|
||||
}else if(res == SPP_IDX_SPP_HEARTBEAT_VAL){
|
||||
if((p_data->write.len == sizeof(heartbeat_s))&&(memcmp(heartbeat_s,p_data->write.value,sizeof(heartbeat_s)) == 0)){
|
||||
heartbeat_count_num = 0;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
else if(res == SPP_IDX_SPP_DATA_RECV_VAL){
|
||||
#ifdef SPP_DEBUG_MODE
|
||||
esp_log_buffer_char(GATTS_TABLE_TAG,(char *)(p_data->write.value),p_data->write.len);
|
||||
#else
|
||||
uart_write_bytes(UART_NUM_0, (char *)(p_data->write.value), p_data->write.len);
|
||||
#endif
|
||||
}else{
|
||||
//TODO:
|
||||
}
|
||||
}else if((p_data->write.is_prep == true)&&(res == SPP_IDX_SPP_DATA_RECV_VAL)){
|
||||
ESP_LOGI(GATTS_TABLE_TAG, "ESP_GATTS_PREP_WRITE_EVT : handle = %d\n", res);
|
||||
store_wr_buffer(p_data);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ESP_GATTS_EXEC_WRITE_EVT:{
|
||||
ESP_LOGI(GATTS_TABLE_TAG, "ESP_GATTS_EXEC_WRITE_EVT\n");
|
||||
if(p_data->exec_write.exec_write_flag){
|
||||
print_write_buffer();
|
||||
free_write_buffer();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ESP_GATTS_MTU_EVT:
|
||||
spp_mtu_size = p_data->mtu.mtu;
|
||||
break;
|
||||
case ESP_GATTS_CONF_EVT:
|
||||
break;
|
||||
case ESP_GATTS_UNREG_EVT:
|
||||
break;
|
||||
case ESP_GATTS_DELETE_EVT:
|
||||
break;
|
||||
case ESP_GATTS_START_EVT:
|
||||
break;
|
||||
case ESP_GATTS_STOP_EVT:
|
||||
break;
|
||||
case ESP_GATTS_CONNECT_EVT:
|
||||
spp_conn_id = p_data->connect.conn_id;
|
||||
spp_gatts_if = gatts_if;
|
||||
is_connected = true;
|
||||
memcpy(&spp_remote_bda,&p_data->connect.remote_bda,sizeof(esp_bd_addr_t));
|
||||
#ifdef SUPPORT_HEARTBEAT
|
||||
uint16_t cmd = 0;
|
||||
xQueueSend(cmd_heartbeat_queue,&cmd,10/portTICK_PERIOD_MS);
|
||||
#endif
|
||||
break;
|
||||
case ESP_GATTS_DISCONNECT_EVT:
|
||||
is_connected = false;
|
||||
enable_data_ntf = false;
|
||||
#ifdef SUPPORT_HEARTBEAT
|
||||
enable_heart_ntf = false;
|
||||
heartbeat_count_num = 0;
|
||||
#endif
|
||||
esp_ble_gap_start_advertising(&spp_adv_params);
|
||||
break;
|
||||
case ESP_GATTS_OPEN_EVT:
|
||||
break;
|
||||
case ESP_GATTS_CANCEL_OPEN_EVT:
|
||||
break;
|
||||
case ESP_GATTS_CLOSE_EVT:
|
||||
break;
|
||||
case ESP_GATTS_LISTEN_EVT:
|
||||
break;
|
||||
case ESP_GATTS_CONGEST_EVT:
|
||||
break;
|
||||
case ESP_GATTS_CREAT_ATTR_TAB_EVT:{
|
||||
ESP_LOGI(GATTS_TABLE_TAG, "The number handle =%x\n",param->add_attr_tab.num_handle);
|
||||
if (param->add_attr_tab.status != ESP_GATT_OK){
|
||||
ESP_LOGE(GATTS_TABLE_TAG, "Create attribute table failed, error code=0x%x", param->add_attr_tab.status);
|
||||
}
|
||||
else if (param->add_attr_tab.num_handle != SPP_IDX_NB){
|
||||
ESP_LOGE(GATTS_TABLE_TAG, "Create attribute table abnormally, num_handle (%d) doesn't equal to HRS_IDX_NB(%d)", param->add_attr_tab.num_handle, SPP_IDX_NB);
|
||||
}
|
||||
else {
|
||||
memcpy(spp_handle_table, param->add_attr_tab.handles, sizeof(spp_handle_table));
|
||||
esp_ble_gatts_start_service(spp_handle_table[SPP_IDX_SVC]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param)
|
||||
{
|
||||
ESP_LOGI(GATTS_TABLE_TAG, "EVT %d, gatts if %d\n", event, gatts_if);
|
||||
|
||||
/* If event is register event, store the gatts_if for each profile */
|
||||
if (event == ESP_GATTS_REG_EVT) {
|
||||
if (param->reg.status == ESP_GATT_OK) {
|
||||
spp_profile_tab[SPP_PROFILE_APP_IDX].gatts_if = gatts_if;
|
||||
} else {
|
||||
ESP_LOGI(GATTS_TABLE_TAG, "Reg app failed, app_id %04x, status %d\n",param->reg.app_id, param->reg.status);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
do {
|
||||
int idx;
|
||||
for (idx = 0; idx < SPP_PROFILE_NUM; idx++) {
|
||||
if (gatts_if == ESP_GATT_IF_NONE || /* ESP_GATT_IF_NONE, not specify a certain gatt_if, need to call every profile cb function */
|
||||
gatts_if == spp_profile_tab[idx].gatts_if) {
|
||||
if (spp_profile_tab[idx].gatts_cb) {
|
||||
spp_profile_tab[idx].gatts_cb(event, gatts_if, param);
|
||||
}
|
||||
}
|
||||
}
|
||||
} while (0);
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
esp_err_t ret;
|
||||
esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
|
||||
|
||||
// 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_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT));
|
||||
|
||||
ret = esp_bt_controller_init(&bt_cfg);
|
||||
if (ret) {
|
||||
ESP_LOGE(GATTS_TABLE_TAG, "%s enable controller failed: %s\n", __func__, esp_err_to_name(ret));
|
||||
return;
|
||||
}
|
||||
|
||||
ret = esp_bt_controller_enable(ESP_BT_MODE_BLE);
|
||||
if (ret) {
|
||||
ESP_LOGE(GATTS_TABLE_TAG, "%s enable controller failed: %s\n", __func__, esp_err_to_name(ret));
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGI(GATTS_TABLE_TAG, "%s init bluetooth\n", __func__);
|
||||
ret = esp_bluedroid_init();
|
||||
if (ret) {
|
||||
ESP_LOGE(GATTS_TABLE_TAG, "%s init bluetooth failed: %s\n", __func__, esp_err_to_name(ret));
|
||||
return;
|
||||
}
|
||||
ret = esp_bluedroid_enable();
|
||||
if (ret) {
|
||||
ESP_LOGE(GATTS_TABLE_TAG, "%s enable bluetooth failed: %s\n", __func__, esp_err_to_name(ret));
|
||||
return;
|
||||
}
|
||||
|
||||
esp_ble_gatts_register_callback(gatts_event_handler);
|
||||
esp_ble_gap_register_callback(gap_event_handler);
|
||||
esp_ble_gatts_app_register(ESP_SPP_APP_ID);
|
||||
|
||||
spp_task_init();
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
/*
|
||||
* DEFINES
|
||||
****************************************************************************************
|
||||
*/
|
||||
//#define SUPPORT_HEARTBEAT
|
||||
//#define SPP_DEBUG_MODE
|
||||
|
||||
#define spp_sprintf(s,...) sprintf((char*)(s), ##__VA_ARGS__)
|
||||
#define SPP_DATA_MAX_LEN (512)
|
||||
#define SPP_CMD_MAX_LEN (20)
|
||||
#define SPP_STATUS_MAX_LEN (20)
|
||||
#define SPP_DATA_BUFF_MAX_LEN (2*1024)
|
||||
///Attributes State Machine
|
||||
enum{
|
||||
SPP_IDX_SVC,
|
||||
|
||||
SPP_IDX_SPP_DATA_RECV_CHAR,
|
||||
SPP_IDX_SPP_DATA_RECV_VAL,
|
||||
|
||||
SPP_IDX_SPP_DATA_NOTIFY_CHAR,
|
||||
SPP_IDX_SPP_DATA_NTY_VAL,
|
||||
SPP_IDX_SPP_DATA_NTF_CFG,
|
||||
|
||||
SPP_IDX_SPP_COMMAND_CHAR,
|
||||
SPP_IDX_SPP_COMMAND_VAL,
|
||||
|
||||
SPP_IDX_SPP_STATUS_CHAR,
|
||||
SPP_IDX_SPP_STATUS_VAL,
|
||||
SPP_IDX_SPP_STATUS_CFG,
|
||||
|
||||
#ifdef SUPPORT_HEARTBEAT
|
||||
SPP_IDX_SPP_HEARTBEAT_CHAR,
|
||||
SPP_IDX_SPP_HEARTBEAT_VAL,
|
||||
SPP_IDX_SPP_HEARTBEAT_CFG,
|
||||
#endif
|
||||
|
||||
SPP_IDX_NB,
|
||||
};
|
||||
@@ -0,0 +1,4 @@
|
||||
#
|
||||
# "main" pseudo-component makefile.
|
||||
#
|
||||
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
|
||||
@@ -0,0 +1,16 @@
|
||||
# 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
|
||||
#
|
||||
# ESP32-specific config
|
||||
#
|
||||
CONFIG_ESP32_ENABLE_STACK_BT=y
|
||||
# CONFIG_ESP32_ENABLE_STACK_NONE is not set
|
||||
CONFIG_MEMMAP_BT=y
|
||||
@@ -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(throughput_client_demo)
|
||||
@@ -0,0 +1,10 @@
|
||||
#
|
||||
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
|
||||
# project subdirectory.
|
||||
#
|
||||
|
||||
PROJECT_NAME := throughput_client_demo
|
||||
|
||||
COMPONENT_ADD_INCLUDEDIRS := components/include
|
||||
|
||||
include $(IDF_PATH)/make/project.mk
|
||||
@@ -0,0 +1,16 @@
|
||||
| Supported Targets | ESP32 |
|
||||
| ----------------- | ----- |
|
||||
|
||||
ESP-IDF BLE throughput GATT CLIENT demo
|
||||
========================
|
||||
|
||||
This is the demo used to test the BLE throughput, this demo should used with throughput server demo together.
|
||||
The throughput of BLE can up to 720-767 Kbps between to ESP32 board.
|
||||
Note:
|
||||
1. In order to maximize throughput, we need to set the uart print baud rate at 921600 or more (idf.py menuconfig --> Component config --> ESP32-specific --> UART console baud rate --> 921600(or 1500000)) and don't print too much log.
|
||||
2. We can only test notify or write throughput at the same time, this demo default to test the notify throughput, if want to test the write throughput,
|
||||
please set: idf.py menuconfig --> Component config --> Example 'GATT CLIENT THROUGHPUT' Config ---> then select the 'test the gattc write throughput' option
|
||||
3. This demo only test unidirectional throughput, if you want to test the bidirectional throughput please change the demo by yourself.
|
||||
4. Should change the CPU frequency to 160MHZ or 240MHz in the idf.py menuconfig --> Component config ---> ESP32-specific ---> CPU frequency (240 MHz or 160 MHz).
|
||||
5. Should change the bluetooth controller and Bluedroid run in different Core in the idf.py menuconfig --> Component config ---> Bluetooth ---> The cpu core which bluetooth controller run (Core 0 (PRO CPU)) & Bluedroid Enable ---> The cpu core which Bluedroid run (Core 1 (APP CPU)).
|
||||
6. In order to maximize throughput, please test in a clean environment without many BLE devices working and both test devices are ESP32.
|
||||
@@ -0,0 +1,2 @@
|
||||
idf_component_register(SRCS "example_ble_client_throughput.c"
|
||||
INCLUDE_DIRS ".")
|
||||
@@ -0,0 +1,14 @@
|
||||
menu "Example 'GATT CLIENT THROUGHPUT' Config"
|
||||
|
||||
config GATTS_NOTIFY_THROUGHPUT
|
||||
bool "test the gatts notify throughput"
|
||||
help
|
||||
If this config item is set, then the 'GATTC_WRITE_THROUGHPUT' config should be close, it can't test both
|
||||
write or notify at the same time at this demo
|
||||
|
||||
config GATTC_WRITE_THROUGHPUT
|
||||
bool "test the gattc write throughput"
|
||||
help
|
||||
If this config item is set, then the 'GATTS_NOTIFY_THROUGHPUT' config should be close, it can't test both
|
||||
write or notify at the same time at this demo
|
||||
endmenu
|
||||
@@ -0,0 +1,4 @@
|
||||
#
|
||||
# "main" pseudo-component makefile.
|
||||
#
|
||||
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
|
||||
@@ -0,0 +1,601 @@
|
||||
/*
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
/****************************************************************************
|
||||
*
|
||||
* This is the demo to test the BLE throughput. It should be used together with throughput_server demo.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include "nvs.h"
|
||||
#include "nvs_flash.h"
|
||||
|
||||
#include "esp_bt.h"
|
||||
#include "esp_gap_ble_api.h"
|
||||
#include "esp_gattc_api.h"
|
||||
#include "esp_gatt_defs.h"
|
||||
#include "esp_bt_main.h"
|
||||
#include "esp_gatt_common_api.h"
|
||||
#include "esp_log.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "freertos/task.h"
|
||||
|
||||
/**********************************************************
|
||||
* Thread/Task reference
|
||||
**********************************************************/
|
||||
#ifdef CONFIG_BLUEDROID_PINNED_TO_CORE
|
||||
#define BLUETOOTH_TASK_PINNED_TO_CORE (CONFIG_BLUEDROID_PINNED_TO_CORE < portNUM_PROCESSORS ? CONFIG_BLUEDROID_PINNED_TO_CORE : tskNO_AFFINITY)
|
||||
#else
|
||||
#define BLUETOOTH_TASK_PINNED_TO_CORE (0)
|
||||
#endif
|
||||
|
||||
#define GATTC_TAG "GATTC_DEMO"
|
||||
#define REMOTE_SERVICE_UUID 0x00FF
|
||||
#define REMOTE_NOTIFY_CHAR_UUID 0xFF01
|
||||
#define PROFILE_NUM 1
|
||||
#define PROFILE_A_APP_ID 0
|
||||
#define INVALID_HANDLE 0
|
||||
#define SECOND_TO_USECOND 1000000
|
||||
|
||||
static const char remote_device_name[] = "THROUGHPUT_DEMO";
|
||||
static bool connect = false;
|
||||
static bool get_server = false;
|
||||
static esp_gattc_char_elem_t *char_elem_result = NULL;
|
||||
static esp_gattc_descr_elem_t *descr_elem_result = NULL;
|
||||
#if (CONFIG_GATTS_NOTIFY_THROUGHPUT)
|
||||
static bool start = false;
|
||||
static uint64_t notify_len = 0;
|
||||
static uint64_t start_time = 0;
|
||||
static uint64_t current_time = 0;
|
||||
#endif /* #if (CONFIG_GATTS_NOTIFY_THROUGHPUT) */
|
||||
|
||||
#if (CONFIG_GATTC_WRITE_THROUGHPUT)
|
||||
#define GATTC_WRITE_LEN 490
|
||||
|
||||
static bool can_send_write = false;
|
||||
static SemaphoreHandle_t gattc_semaphore;
|
||||
uint8_t write_data[GATTC_WRITE_LEN] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0e, 0x0f};
|
||||
#endif /* #if (CONFIG_GATTC_WRITE_THROUGHPUT) */
|
||||
|
||||
static bool is_connect = false;
|
||||
|
||||
/* Declare static functions */
|
||||
static void esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param);
|
||||
static void esp_gattc_cb(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param);
|
||||
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 esp_bt_uuid_t remote_filter_service_uuid = {
|
||||
.len = ESP_UUID_LEN_16,
|
||||
.uuid = {.uuid16 = REMOTE_SERVICE_UUID,},
|
||||
};
|
||||
|
||||
static esp_bt_uuid_t remote_filter_char_uuid = {
|
||||
.len = ESP_UUID_LEN_16,
|
||||
.uuid = {.uuid16 = REMOTE_NOTIFY_CHAR_UUID,},
|
||||
};
|
||||
|
||||
static esp_bt_uuid_t notify_descr_uuid = {
|
||||
.len = ESP_UUID_LEN_16,
|
||||
.uuid = {.uuid16 = ESP_GATT_UUID_CHAR_CLIENT_CONFIG,},
|
||||
};
|
||||
|
||||
static esp_ble_scan_params_t ble_scan_params = {
|
||||
.scan_type = BLE_SCAN_TYPE_ACTIVE,
|
||||
.own_addr_type = BLE_ADDR_TYPE_PUBLIC,
|
||||
.scan_filter_policy = BLE_SCAN_FILTER_ALLOW_ALL,
|
||||
.scan_interval = 0x50,
|
||||
.scan_window = 0x30,
|
||||
.scan_duplicate = BLE_SCAN_DUPLICATE_DISABLE
|
||||
};
|
||||
|
||||
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 char_handle;
|
||||
esp_bd_addr_t remote_bda;
|
||||
};
|
||||
|
||||
/* One gatt-based profile one app_id and one gattc_if, this array will store the gattc_if returned by ESP_GATTS_REG_EVT */
|
||||
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, /* Not get the gatt_if, so initial is ESP_GATT_IF_NONE */
|
||||
},
|
||||
};
|
||||
|
||||
static uint8_t check_sum(uint8_t *addr, uint16_t count)
|
||||
{
|
||||
uint32_t sum = 0;
|
||||
|
||||
if (addr == NULL || count == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
for(int i = 0; i < count; i++) {
|
||||
sum = sum + addr[i];
|
||||
}
|
||||
|
||||
while (sum >> 8) {
|
||||
sum = (sum & 0xff) + (sum >> 8);
|
||||
}
|
||||
|
||||
return (uint8_t)~sum;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
esp_ble_gattc_cb_param_t *p_data = (esp_ble_gattc_cb_param_t *)param;
|
||||
|
||||
switch (event) {
|
||||
case ESP_GATTC_REG_EVT:
|
||||
ESP_LOGI(GATTC_TAG, "REG_EVT");
|
||||
esp_err_t scan_ret = esp_ble_gap_set_scan_params(&ble_scan_params);
|
||||
if (scan_ret){
|
||||
ESP_LOGE(GATTC_TAG, "set scan params error, error code = %x", scan_ret);
|
||||
}
|
||||
break;
|
||||
case ESP_GATTC_CONNECT_EVT: {
|
||||
is_connect = true;
|
||||
ESP_LOGI(GATTC_TAG, "ESP_GATTC_CONNECT_EVT conn_id %d, if %d", p_data->connect.conn_id, gattc_if);
|
||||
gl_profile_tab[PROFILE_A_APP_ID].conn_id = p_data->connect.conn_id;
|
||||
memcpy(gl_profile_tab[PROFILE_A_APP_ID].remote_bda, p_data->connect.remote_bda, sizeof(esp_bd_addr_t));
|
||||
ESP_LOGI(GATTC_TAG, "REMOTE BDA:");
|
||||
esp_log_buffer_hex(GATTC_TAG, gl_profile_tab[PROFILE_A_APP_ID].remote_bda, sizeof(esp_bd_addr_t));
|
||||
esp_err_t mtu_ret = esp_ble_gattc_send_mtu_req (gattc_if, p_data->connect.conn_id);
|
||||
if (mtu_ret){
|
||||
ESP_LOGE(GATTC_TAG, "config MTU error, error code = %x", mtu_ret);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_OPEN_EVT:
|
||||
if (param->open.status != ESP_GATT_OK){
|
||||
ESP_LOGE(GATTC_TAG, "open failed, status %d", p_data->open.status);
|
||||
break;
|
||||
}
|
||||
ESP_LOGI(GATTC_TAG, "open success");
|
||||
break;
|
||||
case ESP_GATTC_CFG_MTU_EVT:
|
||||
if (param->cfg_mtu.status != ESP_GATT_OK){
|
||||
ESP_LOGE(GATTC_TAG,"config mtu failed, error status = %x", param->cfg_mtu.status);
|
||||
}
|
||||
ESP_LOGI(GATTC_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);
|
||||
esp_ble_gattc_search_service(gattc_if, param->cfg_mtu.conn_id, &remote_filter_service_uuid);
|
||||
break;
|
||||
case ESP_GATTC_SEARCH_RES_EVT: {
|
||||
ESP_LOGI(GATTC_TAG, "ESP_GATTC_SEARCH_RES_EVT");
|
||||
esp_gatt_srvc_id_t *srvc_id =(esp_gatt_srvc_id_t *)&p_data->search_res.srvc_id;
|
||||
if (srvc_id->id.uuid.len == ESP_UUID_LEN_16 && srvc_id->id.uuid.uuid.uuid16 == REMOTE_SERVICE_UUID) {
|
||||
ESP_LOGI(GATTC_TAG, "service found");
|
||||
get_server = true;
|
||||
gl_profile_tab[PROFILE_A_APP_ID].service_start_handle = p_data->search_res.start_handle;
|
||||
gl_profile_tab[PROFILE_A_APP_ID].service_end_handle = p_data->search_res.end_handle;
|
||||
ESP_LOGI(GATTC_TAG, "UUID16: %x", srvc_id->id.uuid.uuid.uuid16);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_SEARCH_CMPL_EVT:
|
||||
if (p_data->search_cmpl.status != ESP_GATT_OK){
|
||||
ESP_LOGE(GATTC_TAG, "search service failed, error status = %x", p_data->search_cmpl.status);
|
||||
break;
|
||||
}
|
||||
ESP_LOGI(GATTC_TAG, "ESP_GATTC_SEARCH_CMPL_EVT");
|
||||
if (get_server){
|
||||
uint16_t count = 0;
|
||||
esp_gatt_status_t status = esp_ble_gattc_get_attr_count( gattc_if,
|
||||
p_data->search_cmpl.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 (status != ESP_GATT_OK){
|
||||
ESP_LOGE(GATTC_TAG, "esp_ble_gattc_get_attr_count error");
|
||||
}
|
||||
|
||||
if (count > 0){
|
||||
char_elem_result = (esp_gattc_char_elem_t *)malloc(sizeof(esp_gattc_char_elem_t) * count);
|
||||
if (!char_elem_result){
|
||||
ESP_LOGE(GATTC_TAG, "gattc no mem");
|
||||
}else{
|
||||
status = esp_ble_gattc_get_char_by_uuid( gattc_if,
|
||||
p_data->search_cmpl.conn_id,
|
||||
gl_profile_tab[PROFILE_A_APP_ID].service_start_handle,
|
||||
gl_profile_tab[PROFILE_A_APP_ID].service_end_handle,
|
||||
remote_filter_char_uuid,
|
||||
char_elem_result,
|
||||
&count);
|
||||
if (status != ESP_GATT_OK){
|
||||
ESP_LOGE(GATTC_TAG, "esp_ble_gattc_get_char_by_uuid error");
|
||||
}
|
||||
|
||||
/* Every service has only one char in our 'throughput_server' demo, so we use first 'char_elem_result' */
|
||||
if (count > 0 && (char_elem_result[0].properties & ESP_GATT_CHAR_PROP_BIT_NOTIFY)){
|
||||
gl_profile_tab[PROFILE_A_APP_ID].char_handle = char_elem_result[0].char_handle;
|
||||
esp_ble_gattc_register_for_notify (gattc_if, gl_profile_tab[PROFILE_A_APP_ID].remote_bda, char_elem_result[0].char_handle);
|
||||
}
|
||||
}
|
||||
/* free char_elem_result */
|
||||
free(char_elem_result);
|
||||
}else{
|
||||
ESP_LOGE(GATTC_TAG, "no char found");
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
|
||||
ESP_LOGI(GATTC_TAG, "ESP_GATTC_REG_FOR_NOTIFY_EVT");
|
||||
if (p_data->reg_for_notify.status != ESP_GATT_OK){
|
||||
ESP_LOGE(GATTC_TAG, "REG FOR NOTIFY failed: error status = %d", p_data->reg_for_notify.status);
|
||||
}else{
|
||||
uint16_t count = 0;
|
||||
uint16_t notify_en = 1;
|
||||
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_DESCRIPTOR,
|
||||
gl_profile_tab[PROFILE_A_APP_ID].service_start_handle,
|
||||
gl_profile_tab[PROFILE_A_APP_ID].service_end_handle,
|
||||
gl_profile_tab[PROFILE_A_APP_ID].char_handle,
|
||||
&count);
|
||||
if (ret_status != ESP_GATT_OK){
|
||||
ESP_LOGE(GATTC_TAG, "esp_ble_gattc_get_attr_count error");
|
||||
}
|
||||
if (count > 0){
|
||||
descr_elem_result = malloc(sizeof(esp_gattc_descr_elem_t) * count);
|
||||
if (!descr_elem_result){
|
||||
ESP_LOGE(GATTC_TAG, "malloc error, gattc no mem");
|
||||
}else{
|
||||
ret_status = esp_ble_gattc_get_descr_by_char_handle( gattc_if,
|
||||
gl_profile_tab[PROFILE_A_APP_ID].conn_id,
|
||||
p_data->reg_for_notify.handle,
|
||||
notify_descr_uuid,
|
||||
descr_elem_result,
|
||||
&count);
|
||||
if (ret_status != ESP_GATT_OK){
|
||||
ESP_LOGE(GATTC_TAG, "esp_ble_gattc_get_descr_by_char_handle error");
|
||||
}
|
||||
|
||||
/* Every char has only one descriptor in our 'throughput_server' demo, so we use first 'descr_elem_result' */
|
||||
if (count > 0 && descr_elem_result[0].uuid.len == ESP_UUID_LEN_16 && descr_elem_result[0].uuid.uuid.uuid16 == ESP_GATT_UUID_CHAR_CLIENT_CONFIG){
|
||||
ret_status = esp_ble_gattc_write_char_descr( gattc_if,
|
||||
gl_profile_tab[PROFILE_A_APP_ID].conn_id,
|
||||
descr_elem_result[0].handle,
|
||||
sizeof(notify_en),
|
||||
(uint8_t *)¬ify_en,
|
||||
ESP_GATT_WRITE_TYPE_RSP,
|
||||
ESP_GATT_AUTH_REQ_NONE);
|
||||
}
|
||||
|
||||
if (ret_status != ESP_GATT_OK){
|
||||
ESP_LOGE(GATTC_TAG, "esp_ble_gattc_write_char_descr error");
|
||||
}
|
||||
|
||||
/* free descr_elem_result */
|
||||
free(descr_elem_result);
|
||||
}
|
||||
}
|
||||
else{
|
||||
ESP_LOGE(GATTC_TAG, "decsr not found");
|
||||
}
|
||||
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_NOTIFY_EVT: {
|
||||
#if (CONFIG_GATTS_NOTIFY_THROUGHPUT)
|
||||
if (p_data->notify.is_notify &&
|
||||
(p_data->notify.value[p_data->notify.value_len - 1] ==
|
||||
check_sum(p_data->notify.value, p_data->notify.value_len - 1))){
|
||||
notify_len += p_data->notify.value_len;
|
||||
} else {
|
||||
ESP_LOGE(GATTC_TAG, "ESP_GATTC_NOTIFY_EVT, receive indicate value:");
|
||||
}
|
||||
if (start == false) {
|
||||
start_time = esp_timer_get_time();
|
||||
start = true;
|
||||
break;
|
||||
}
|
||||
|
||||
#else /* #if (CONFIG_GATTS_NOTIFY_THROUGHPUT) */
|
||||
esp_log_buffer_hex(GATTC_TAG, p_data->notify.value, p_data->notify.value_len);
|
||||
#endif /* #if (CONFIG_GATTS_NOTIFY_THROUGHPUT) */
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_WRITE_DESCR_EVT:
|
||||
if (p_data->write.status != ESP_GATT_OK){
|
||||
ESP_LOGE(GATTC_TAG, "write descr failed, error status = %x", p_data->write.status);
|
||||
break;
|
||||
}
|
||||
ESP_LOGI(GATTC_TAG, "write descr success ");
|
||||
#if (CONFIG_GATTC_WRITE_THROUGHPUT)
|
||||
can_send_write = true;
|
||||
xSemaphoreGive(gattc_semaphore);
|
||||
#endif /* #if (CONFIG_GATTC_WRITE_THROUGHPUT) */
|
||||
break;
|
||||
case ESP_GATTC_SRVC_CHG_EVT: {
|
||||
esp_bd_addr_t bda;
|
||||
memcpy(bda, p_data->srvc_chg.remote_bda, sizeof(esp_bd_addr_t));
|
||||
ESP_LOGI(GATTC_TAG, "ESP_GATTC_SRVC_CHG_EVT, bd_addr:");
|
||||
esp_log_buffer_hex(GATTC_TAG, bda, sizeof(esp_bd_addr_t));
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_WRITE_CHAR_EVT:
|
||||
if (p_data->write.status != ESP_GATT_OK) {
|
||||
ESP_LOGE(GATTC_TAG, "write char failed, error status = %x", p_data->write.status);
|
||||
break;
|
||||
}
|
||||
ESP_LOGI(GATTC_TAG, "write char success ");
|
||||
break;
|
||||
case ESP_GATTC_DISCONNECT_EVT:
|
||||
is_connect = false;
|
||||
get_server = false;
|
||||
#if (CONFIG_GATTS_NOTIFY_THROUGHPUT)
|
||||
start = false;
|
||||
start_time = 0;
|
||||
current_time = 0;
|
||||
notify_len = 0;
|
||||
#endif /* #if (CONFIG_GATTS_NOTIFY_THROUGHPUT) */
|
||||
ESP_LOGI(GATTC_TAG, "ESP_GATTC_DISCONNECT_EVT, reason = %d", p_data->disconnect.reason);
|
||||
break;
|
||||
case ESP_GATTC_CONGEST_EVT:
|
||||
#if (CONFIG_GATTC_WRITE_THROUGHPUT)
|
||||
if (param->congest.congested) {
|
||||
can_send_write = false;
|
||||
} else {
|
||||
can_send_write = true;
|
||||
xSemaphoreGive(gattc_semaphore);
|
||||
}
|
||||
#endif /* #if (CONFIG_GATTC_WRITE_THROUGHPUT) */
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param)
|
||||
{
|
||||
uint8_t *adv_name = NULL;
|
||||
uint8_t adv_name_len = 0;
|
||||
switch (event) {
|
||||
case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT: {
|
||||
//the unit of the duration is second
|
||||
uint32_t duration = 30;
|
||||
esp_ble_gap_start_scanning(duration);
|
||||
break;
|
||||
}
|
||||
case ESP_GAP_BLE_SCAN_START_COMPLETE_EVT:
|
||||
//scan start complete event to indicate scan start successfully or failed
|
||||
if (param->scan_start_cmpl.status != ESP_BT_STATUS_SUCCESS) {
|
||||
ESP_LOGE(GATTC_TAG, "scan start failed, error status = %x", param->scan_start_cmpl.status);
|
||||
break;
|
||||
}
|
||||
ESP_LOGI(GATTC_TAG, "scan start success");
|
||||
|
||||
break;
|
||||
case ESP_GAP_BLE_SCAN_RESULT_EVT: {
|
||||
esp_ble_gap_cb_param_t *scan_result = (esp_ble_gap_cb_param_t *)param;
|
||||
switch (scan_result->scan_rst.search_evt) {
|
||||
case ESP_GAP_SEARCH_INQ_RES_EVT:
|
||||
esp_log_buffer_hex(GATTC_TAG, scan_result->scan_rst.bda, 6);
|
||||
ESP_LOGI(GATTC_TAG, "searched Adv Data Len %d, Scan Response Len %d", scan_result->scan_rst.adv_data_len, scan_result->scan_rst.scan_rsp_len);
|
||||
adv_name = esp_ble_resolve_adv_data(scan_result->scan_rst.ble_adv,
|
||||
ESP_BLE_AD_TYPE_NAME_CMPL, &adv_name_len);
|
||||
ESP_LOGI(GATTC_TAG, "searched Device Name Len %d", adv_name_len);
|
||||
esp_log_buffer_char(GATTC_TAG, adv_name, adv_name_len);
|
||||
ESP_LOGI(GATTC_TAG, "\n");
|
||||
if (adv_name != NULL) {
|
||||
if (strlen(remote_device_name) == adv_name_len && strncmp((char *)adv_name, remote_device_name, adv_name_len) == 0) {
|
||||
ESP_LOGI(GATTC_TAG, "searched device %s\n", remote_device_name);
|
||||
if (connect == false) {
|
||||
connect = true;
|
||||
ESP_LOGI(GATTC_TAG, "connect to the remote device.");
|
||||
esp_ble_gap_set_prefer_conn_params(scan_result->scan_rst.bda, 32, 32, 0, 600);
|
||||
esp_ble_gap_stop_scanning();
|
||||
esp_ble_gattc_open(gl_profile_tab[PROFILE_A_APP_ID].gattc_if, scan_result->scan_rst.bda, BLE_ADDR_TYPE_PUBLIC, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ESP_GAP_SEARCH_INQ_CMPL_EVT:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT:
|
||||
if (param->scan_stop_cmpl.status != ESP_BT_STATUS_SUCCESS){
|
||||
ESP_LOGE(GATTC_TAG, "scan stop failed, error status = %x", param->scan_stop_cmpl.status);
|
||||
break;
|
||||
}
|
||||
ESP_LOGI(GATTC_TAG, "stop scan successfully");
|
||||
break;
|
||||
|
||||
case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT:
|
||||
if (param->adv_stop_cmpl.status != ESP_BT_STATUS_SUCCESS){
|
||||
ESP_LOGE(GATTC_TAG, "adv stop failed, error status = %x", param->adv_stop_cmpl.status);
|
||||
break;
|
||||
}
|
||||
ESP_LOGI(GATTC_TAG, "stop adv successfully");
|
||||
break;
|
||||
case ESP_GAP_BLE_UPDATE_CONN_PARAMS_EVT:
|
||||
ESP_LOGI(GATTC_TAG, "update connection params status = %d, min_int = %d, max_int = %d,conn_int = %d,latency = %d, timeout = %d",
|
||||
param->update_conn_params.status,
|
||||
param->update_conn_params.min_int,
|
||||
param->update_conn_params.max_int,
|
||||
param->update_conn_params.conn_int,
|
||||
param->update_conn_params.latency,
|
||||
param->update_conn_params.timeout);
|
||||
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 is register event, store the gattc_if for each profile */
|
||||
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(GATTC_TAG, "reg app failed, app_id %04x, status %d",
|
||||
param->reg.app_id,
|
||||
param->reg.status);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* If the gattc_if equal to profile A, call profile A cb handler,
|
||||
* so here call each profile's callback */
|
||||
do {
|
||||
int idx;
|
||||
for (idx = 0; idx < PROFILE_NUM; idx++) {
|
||||
if (gattc_if == ESP_GATT_IF_NONE || /* ESP_GATT_IF_NONE, not specify a certain gatt_if, need to call every profile cb function */
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
} while (0);
|
||||
}
|
||||
|
||||
static void throughput_client_task(void *param)
|
||||
{
|
||||
vTaskDelay(2000 / portTICK_PERIOD_MS);
|
||||
#if (CONFIG_GATTC_WRITE_THROUGHPUT)
|
||||
uint8_t sum = check_sum(write_data, sizeof(write_data) - 1);
|
||||
write_data[GATTC_WRITE_LEN - 1] = sum;
|
||||
#endif /* #if (CONFIG_GATTC_WRITE_THROUGHPUT) */
|
||||
|
||||
while(1) {
|
||||
#if (CONFIG_GATTS_NOTIFY_THROUGHPUT)
|
||||
vTaskDelay(2000 / portTICK_PERIOD_MS);
|
||||
if(is_connect){
|
||||
uint32_t bit_rate = 0;
|
||||
if (start_time) {
|
||||
current_time = esp_timer_get_time();
|
||||
bit_rate = notify_len * SECOND_TO_USECOND / (current_time - start_time);
|
||||
ESP_LOGI(GATTC_TAG, "Notify Bit rate = %d Byte/s, = %d bit/s, time = %ds",
|
||||
bit_rate, bit_rate<<3, (int)((current_time - start_time) / SECOND_TO_USECOND));
|
||||
} else {
|
||||
ESP_LOGI(GATTC_TAG, "Notify Bit rate = 0 Byte/s, = 0 bit/s");
|
||||
}
|
||||
}
|
||||
#endif /* #if (CONFIG_GATTS_NOTIFY_THROUGHPUT) */
|
||||
#if (CONFIG_GATTC_WRITE_THROUGHPUT)
|
||||
if (!can_send_write) {
|
||||
int res = xSemaphoreTake(gattc_semaphore, portMAX_DELAY);
|
||||
assert(res == pdTRUE);
|
||||
} else {
|
||||
if (is_connect) {
|
||||
int free_buff_num = esp_ble_get_cur_sendable_packets_num(gl_profile_tab[PROFILE_A_APP_ID].conn_id);
|
||||
if(free_buff_num > 0) {
|
||||
for( ; free_buff_num > 0; free_buff_num--) {
|
||||
// the app data set to 490 just for divided into two packages to send in the low layer
|
||||
// when the packet length set to 251.
|
||||
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].char_handle,
|
||||
sizeof(write_data), write_data,
|
||||
ESP_GATT_WRITE_TYPE_NO_RSP,
|
||||
ESP_GATT_AUTH_REQ_NONE);
|
||||
}
|
||||
} else { //Add the vTaskDelay to prevent this task from consuming the CPU all the time, causing low-priority tasks to not be executed at all.
|
||||
vTaskDelay( 10 / portTICK_PERIOD_MS );
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif /* #if (CONFIG_GATTC_WRITE_THROUGHPUT) */
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
// Initialize NVS.
|
||||
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();
|
||||
ret = esp_bt_controller_init(&bt_cfg);
|
||||
if (ret) {
|
||||
ESP_LOGE(GATTC_TAG, "%s initialize controller failed, error code = %x\n", __func__, ret);
|
||||
return;
|
||||
}
|
||||
|
||||
ret = esp_bt_controller_enable(ESP_BT_MODE_BLE);
|
||||
if (ret) {
|
||||
ESP_LOGE(GATTC_TAG, "%s enable controller failed, error code = %x\n", __func__, ret);
|
||||
return;
|
||||
}
|
||||
|
||||
ret = esp_bluedroid_init();
|
||||
if (ret) {
|
||||
ESP_LOGE(GATTC_TAG, "%s init bluetooth failed, error code = %x\n", __func__, ret);
|
||||
return;
|
||||
}
|
||||
|
||||
ret = esp_bluedroid_enable();
|
||||
if (ret) {
|
||||
ESP_LOGE(GATTC_TAG, "%s enable bluetooth failed, error code = %x\n", __func__, ret);
|
||||
return;
|
||||
}
|
||||
|
||||
//register the callback function to the gap module
|
||||
ret = esp_ble_gap_register_callback(esp_gap_cb);
|
||||
if (ret){
|
||||
ESP_LOGE(GATTC_TAG, "%s gap register failed, error code = %x\n", __func__, ret);
|
||||
return;
|
||||
}
|
||||
|
||||
//register the callback function to the gattc module
|
||||
ret = esp_ble_gattc_register_callback(esp_gattc_cb);
|
||||
if(ret){
|
||||
ESP_LOGE(GATTC_TAG, "%s gattc register failed, error code = %x\n", __func__, ret);
|
||||
return;
|
||||
}
|
||||
|
||||
ret = esp_ble_gattc_app_register(PROFILE_A_APP_ID);
|
||||
if (ret){
|
||||
ESP_LOGE(GATTC_TAG, "%s gattc app register failed, error code = %x\n", __func__, ret);
|
||||
}
|
||||
// set the maximum MTU for used.
|
||||
esp_err_t local_mtu_ret = esp_ble_gatt_set_local_mtu(517);
|
||||
if (local_mtu_ret){
|
||||
ESP_LOGE(GATTC_TAG, "set local MTU failed, error code = %x", local_mtu_ret);
|
||||
}
|
||||
// The task is only created on the CPU core that Bluetooth is working on,
|
||||
// preventing the sending task from using the un-updated Bluetooth state on another CPU.
|
||||
xTaskCreatePinnedToCore(&throughput_client_task, "throughput_client_task", 4096, NULL, 10, NULL, BLUETOOTH_TASK_PINNED_TO_CORE);
|
||||
|
||||
#if (CONFIG_GATTC_WRITE_THROUGHPUT)
|
||||
gattc_semaphore = xSemaphoreCreateBinary();
|
||||
if (!gattc_semaphore) {
|
||||
ESP_LOGE(GATTC_TAG, "%s, init fail, the gattc semaphore create fail.", __func__);
|
||||
return;
|
||||
}
|
||||
#endif /* #if (CONFIG_GATTC_WRITE_THROUGHPUT) */
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
# Override some defaults so BT stack is enabled
|
||||
# by default in this example
|
||||
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_BTDM_CTRL_BLE_MAX_CONN=9
|
||||
CONFIG_GATTS_NOTIFY_THROUGHPUT=y
|
||||
CONFIG_BTDM_MODEM_SLEEP=n
|
||||
CONFIG_BTDM_CTRL_PINNED_TO_CORE_1=y
|
||||
CONFIG_BTDM_CTRL_PINNED_TO_CORE=1
|
||||
#
|
||||
# Serial flasher config
|
||||
#
|
||||
CONFIG_ESPTOOLPY_MONITOR_BAUD_921600B=y
|
||||
CONFIG_ESPTOOLPY_MONITOR_BAUD=921600
|
||||
#
|
||||
# ESP32-specific
|
||||
#
|
||||
CONFIG_ESP32_TRACEMEM_RESERVE_DRAM=0x0
|
||||
CONFIG_ESP32_ENABLE_COREDUMP_TO_NONE=y
|
||||
CONFIG_ESP_CONSOLE_UART_BAUDRATE=921600
|
||||
@@ -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(throughput_server_demo)
|
||||
@@ -0,0 +1,10 @@
|
||||
#
|
||||
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
|
||||
# project subdirectory.
|
||||
#
|
||||
|
||||
PROJECT_NAME := throughput_server_demo
|
||||
|
||||
COMPONENT_ADD_INCLUDEDIRS := components/include
|
||||
|
||||
include $(IDF_PATH)/make/project.mk
|
||||
@@ -0,0 +1,17 @@
|
||||
| Supported Targets | ESP32 |
|
||||
| ----------------- | ----- |
|
||||
|
||||
ESP-IDF BLE throughput GATT SERVER demo
|
||||
========================
|
||||
|
||||
This is the demo used to test the BLE throughput, this demo should used with throughput client demo together.
|
||||
The throughput of BLE can up to 720-767 Kbps between to ESP32 board.
|
||||
Note:
|
||||
1. In order to maximize throughput, we need to set the uart print baud rate at 921600 or more (idf.py menuconfig --> Component config --> ESP32-specific --> UART console baud rate --> 921600(or 1500000)) and don't print too much log;
|
||||
2. We can only test notify or write throughput at the same time, this demo default to test the notify throughput, if want to test the write throughput,
|
||||
please set: idf.py menuconfig --> Component config --> Example 'GATT SERVER THROUGHPUT' Config ---> then select the 'test the gattc write throughput' option
|
||||
3. This demo only test unidirectional throughput, if you want to test the bidirectional throughput please change the demo by yourself.
|
||||
4. Should change the CPU frequency to 160MHz or 240MHz in the idf.py menuconfig --> Component config ---> ESP32-specific ---> CPU frequency (160MHz or 240 MHz)
|
||||
5. Should change the bluetooth controller and Bluedroid run in different Core in the idf.py menuconfig --> Component config ---> Bluetooth ---> The cpu core which bluetooth controller run (Core 0 (PRO CPU)) & Bluedroid Enable ---> The cpu core which Bluedroid run (Core 1 (APP CPU))
|
||||
6. In order to maximize throughput, please test in a clean environment without many BLE devices working and both test devices are ESP32.
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
idf_component_register(SRCS "example_ble_server_throughput.c"
|
||||
INCLUDE_DIRS ".")
|
||||
@@ -0,0 +1,27 @@
|
||||
menu "Example 'GATT SERVER THROUGHPUT' Config"
|
||||
|
||||
config EXAMPLE_SET_RAW_ADV_DATA
|
||||
bool "Use raw data for advertising packets and scan response data"
|
||||
help
|
||||
If this config item is set, raw binary data will be used to generate advertising & scan response data.
|
||||
This option uses the esp_ble_gap_config_adv_data_raw() and esp_ble_gap_config_scan_rsp_data_raw()
|
||||
functions.
|
||||
|
||||
If this config item is unset, advertising & scan response data is provided via a higher-level
|
||||
esp_ble_adv_data_t structure. The lower layer will generate the BLE packets. This option has higher
|
||||
overhead at runtime.
|
||||
|
||||
config EXAMPLE_GATTS_NOTIFY_THROUGHPUT
|
||||
bool "test the gatts notify throughput"
|
||||
help
|
||||
If this config item is set, then the 'EXAMPLE_GATTC_WRITE_THROUGHPUT' config should be close, it can't test
|
||||
both write or notify at the same time at this demo
|
||||
|
||||
config EXAMPLE_GATTC_WRITE_THROUGHPUT
|
||||
bool "test the gattc write throughput"
|
||||
help
|
||||
If this config item is set, then the 'EXAMPLE_GATTS_NOTIFY_THROUGHPUT' config should be close, it can't
|
||||
test both write or notify at the same time at this demo
|
||||
endmenu
|
||||
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
#
|
||||
# "main" pseudo-component makefile.
|
||||
#
|
||||
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
|
||||
@@ -0,0 +1,725 @@
|
||||
/*
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
/****************************************************************************
|
||||
*
|
||||
* This is the demo to test the BLE throughput. It should be used together with throughput_client demo.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/event_groups.h"
|
||||
#include "freertos/semphr.h"
|
||||
|
||||
#include "esp_system.h"
|
||||
#include "esp_log.h"
|
||||
#include "nvs_flash.h"
|
||||
#include "esp_bt.h"
|
||||
#include "esp_gap_ble_api.h"
|
||||
#include "esp_gatts_api.h"
|
||||
#include "esp_bt_defs.h"
|
||||
#include "esp_bt_main.h"
|
||||
#include "esp_gatt_common_api.h"
|
||||
|
||||
#include "sdkconfig.h"
|
||||
|
||||
/**********************************************************
|
||||
* Thread/Task reference
|
||||
**********************************************************/
|
||||
#ifdef CONFIG_BLUEDROID_PINNED_TO_CORE
|
||||
#define BLUETOOTH_TASK_PINNED_TO_CORE (CONFIG_BLUEDROID_PINNED_TO_CORE < portNUM_PROCESSORS ? CONFIG_BLUEDROID_PINNED_TO_CORE : tskNO_AFFINITY)
|
||||
#else
|
||||
#define BLUETOOTH_TASK_PINNED_TO_CORE (0)
|
||||
#endif
|
||||
|
||||
#define SECOND_TO_USECOND 1000000
|
||||
|
||||
#define GATTS_TAG "GATTS_DEMO"
|
||||
|
||||
#if (CONFIG_EXAMPLE_GATTS_NOTIFY_THROUGHPUT)
|
||||
#define GATTS_NOTIFY_LEN 490
|
||||
static SemaphoreHandle_t gatts_semaphore;
|
||||
static bool can_send_notify = false;
|
||||
static uint8_t indicate_data[GATTS_NOTIFY_LEN] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a};
|
||||
|
||||
#endif /* #if (CONFIG_EXAMPLE_GATTS_NOTIFY_THROUGHPUT) */
|
||||
|
||||
#if (CONFIG_EXAMPLE_GATTC_WRITE_THROUGHPUT)
|
||||
static bool start = false;
|
||||
static uint64_t write_len = 0;
|
||||
static uint64_t start_time = 0;
|
||||
static uint64_t current_time = 0;
|
||||
#endif /* #if (CONFIG_EXAMPLE_GATTC_WRITE_THROUGHPUT) */
|
||||
|
||||
static bool is_connect = false;
|
||||
///Declare the static function
|
||||
static void gatts_profile_a_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param);
|
||||
|
||||
#define GATTS_SERVICE_UUID_TEST_A 0x00FF
|
||||
#define GATTS_CHAR_UUID_TEST_A 0xFF01
|
||||
#define GATTS_DESCR_UUID_TEST_A 0x3333
|
||||
#define GATTS_NUM_HANDLE_TEST_A 4
|
||||
|
||||
#define GATTS_SERVICE_UUID_TEST_B 0x00EE
|
||||
#define GATTS_CHAR_UUID_TEST_B 0xEE01
|
||||
#define GATTS_DESCR_UUID_TEST_B 0x2222
|
||||
#define GATTS_NUM_HANDLE_TEST_B 4
|
||||
|
||||
#define TEST_DEVICE_NAME "THROUGHPUT_DEMO"
|
||||
#define TEST_MANUFACTURER_DATA_LEN 17
|
||||
|
||||
#define GATTS_DEMO_CHAR_VAL_LEN_MAX 0x40
|
||||
|
||||
#define PREPARE_BUF_MAX_SIZE 1024
|
||||
|
||||
static uint8_t char1_str[] = {0x11,0x22,0x33};
|
||||
static esp_gatt_char_prop_t a_property = 0;
|
||||
|
||||
static esp_attr_value_t gatts_demo_char1_val =
|
||||
{
|
||||
.attr_max_len = GATTS_DEMO_CHAR_VAL_LEN_MAX,
|
||||
.attr_len = sizeof(char1_str),
|
||||
.attr_value = char1_str,
|
||||
};
|
||||
|
||||
static uint8_t adv_config_done = 0;
|
||||
#define adv_config_flag (1 << 0)
|
||||
#define scan_rsp_config_flag (1 << 1)
|
||||
|
||||
#ifdef CONFIG_EXAMPLE_SET_RAW_ADV_DATA
|
||||
static uint8_t raw_adv_data[] = {
|
||||
0x02, 0x01, 0x06,
|
||||
0x02, 0x0a, 0xeb, 0x03, 0x03, 0xab, 0xcd
|
||||
};
|
||||
static uint8_t raw_scan_rsp_data[] = {
|
||||
0x0f, 0x09, 0x45, 0x53, 0x50, 0x5f, 0x47, 0x41, 0x54, 0x54, 0x53, 0x5f, 0x44,
|
||||
0x45, 0x4d, 0x4f
|
||||
};
|
||||
#else
|
||||
|
||||
static uint8_t adv_service_uuid128[32] = {
|
||||
/* LSB <--------------------------------------------------------------------------------> MSB */
|
||||
//first uuid, 16bit, [12],[13] is the value
|
||||
0xfb, 0x34, 0x9b, 0x5f, 0x80, 0x00, 0x00, 0x80, 0x00, 0x10, 0x00, 0x00, 0xEE, 0x00, 0x00, 0x00,
|
||||
//second uuid, 32bit, [12], [13], [14], [15] is the value
|
||||
0xfb, 0x34, 0x9b, 0x5f, 0x80, 0x00, 0x00, 0x80, 0x00, 0x10, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00,
|
||||
};
|
||||
|
||||
// The length of adv data must be less than 31 bytes
|
||||
//static uint8_t test_manufacturer[TEST_MANUFACTURER_DATA_LEN] = {0x12, 0x23, 0x45, 0x56};
|
||||
//adv data
|
||||
static esp_ble_adv_data_t adv_data = {
|
||||
.set_scan_rsp = false,
|
||||
.include_name = true,
|
||||
.include_txpower = true,
|
||||
.min_interval = 0x0006, //slave connection min interval, Time = min_interval * 1.25 msec
|
||||
.max_interval = 0x000C, //slave connection max interval, Time = max_interval * 1.25 msec
|
||||
.appearance = 0x00,
|
||||
.manufacturer_len = 0, //TEST_MANUFACTURER_DATA_LEN,
|
||||
.p_manufacturer_data = NULL, //&test_manufacturer[0],
|
||||
.service_data_len = 0,
|
||||
.p_service_data = NULL,
|
||||
.service_uuid_len = 32,
|
||||
.p_service_uuid = adv_service_uuid128,
|
||||
.flag = (ESP_BLE_ADV_FLAG_GEN_DISC | ESP_BLE_ADV_FLAG_BREDR_NOT_SPT),
|
||||
};
|
||||
// scan response data
|
||||
static esp_ble_adv_data_t scan_rsp_data = {
|
||||
.set_scan_rsp = true,
|
||||
.include_name = true,
|
||||
.include_txpower = true,
|
||||
.min_interval = 0x0006,
|
||||
.max_interval = 0x000C,
|
||||
.appearance = 0x00,
|
||||
.manufacturer_len = 0, //TEST_MANUFACTURER_DATA_LEN,
|
||||
.p_manufacturer_data = NULL, //&test_manufacturer[0],
|
||||
.service_data_len = 0,
|
||||
.p_service_data = NULL,
|
||||
.service_uuid_len = 32,
|
||||
.p_service_uuid = adv_service_uuid128,
|
||||
.flag = (ESP_BLE_ADV_FLAG_GEN_DISC | ESP_BLE_ADV_FLAG_BREDR_NOT_SPT),
|
||||
};
|
||||
|
||||
#endif /* CONFIG_EXAMPLE_SET_RAW_ADV_DATA */
|
||||
|
||||
static esp_ble_adv_params_t adv_params = {
|
||||
.adv_int_min = 0x20,
|
||||
.adv_int_max = 0x40,
|
||||
.adv_type = ADV_TYPE_IND,
|
||||
.own_addr_type = BLE_ADDR_TYPE_PUBLIC,
|
||||
//.peer_addr =
|
||||
//.peer_addr_type =
|
||||
.channel_map = ADV_CHNL_ALL,
|
||||
.adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY,
|
||||
};
|
||||
|
||||
#define PROFILE_NUM 1
|
||||
#define PROFILE_A_APP_ID 0
|
||||
|
||||
struct gatts_profile_inst {
|
||||
esp_gatts_cb_t gatts_cb;
|
||||
uint16_t gatts_if;
|
||||
uint16_t app_id;
|
||||
uint16_t conn_id;
|
||||
uint16_t service_handle;
|
||||
esp_gatt_srvc_id_t service_id;
|
||||
uint16_t char_handle;
|
||||
esp_bt_uuid_t char_uuid;
|
||||
esp_gatt_perm_t perm;
|
||||
esp_gatt_char_prop_t property;
|
||||
uint16_t descr_handle;
|
||||
esp_bt_uuid_t descr_uuid;
|
||||
};
|
||||
|
||||
/* One gatt-based profile one app_id and one gatts_if, this array will store the gatts_if returned by ESP_GATTS_REG_EVT */
|
||||
static struct gatts_profile_inst gl_profile_tab[PROFILE_NUM] = {
|
||||
[PROFILE_A_APP_ID] = {
|
||||
.gatts_cb = gatts_profile_a_event_handler,
|
||||
.gatts_if = ESP_GATT_IF_NONE, /* Not get the gatt_if, so initial is ESP_GATT_IF_NONE */
|
||||
},
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
uint8_t *prepare_buf;
|
||||
int prepare_len;
|
||||
} prepare_type_env_t;
|
||||
|
||||
static prepare_type_env_t a_prepare_write_env;
|
||||
|
||||
void example_write_event_env(esp_gatt_if_t gatts_if, prepare_type_env_t *prepare_write_env, esp_ble_gatts_cb_param_t *param);
|
||||
void example_exec_write_event_env(prepare_type_env_t *prepare_write_env, esp_ble_gatts_cb_param_t *param);
|
||||
|
||||
static uint8_t check_sum(uint8_t *addr, uint16_t count)
|
||||
{
|
||||
uint32_t sum = 0;
|
||||
|
||||
if (addr == NULL || count == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
for(int i = 0; i < count; i++) {
|
||||
sum = sum + addr[i];
|
||||
}
|
||||
|
||||
while (sum >> 8) {
|
||||
sum = (sum & 0xff) + (sum >> 8);
|
||||
}
|
||||
|
||||
return (uint8_t)~sum;
|
||||
}
|
||||
|
||||
|
||||
static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param)
|
||||
{
|
||||
switch (event) {
|
||||
#ifdef CONFIG_EXAMPLE_SET_RAW_ADV_DATA
|
||||
case ESP_GAP_BLE_ADV_DATA_RAW_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_SCAN_RSP_DATA_RAW_SET_COMPLETE_EVT:
|
||||
adv_config_done &= (~scan_rsp_config_flag);
|
||||
if (adv_config_done==0){
|
||||
esp_ble_gap_start_advertising(&adv_params);
|
||||
}
|
||||
break;
|
||||
#else
|
||||
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_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;
|
||||
#endif
|
||||
case ESP_GAP_BLE_ADV_START_COMPLETE_EVT:
|
||||
//advertising start complete event to indicate advertising start successfully or failed
|
||||
if (param->adv_start_cmpl.status != ESP_BT_STATUS_SUCCESS) {
|
||||
ESP_LOGE(GATTS_TAG, "Advertising start failed\n");
|
||||
}
|
||||
break;
|
||||
case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT:
|
||||
if (param->adv_stop_cmpl.status != ESP_BT_STATUS_SUCCESS) {
|
||||
ESP_LOGE(GATTS_TAG, "Advertising stop failed\n");
|
||||
}
|
||||
else {
|
||||
ESP_LOGI(GATTS_TAG, "Stop adv successfully\n");
|
||||
}
|
||||
break;
|
||||
case ESP_GAP_BLE_UPDATE_CONN_PARAMS_EVT:
|
||||
ESP_LOGI(GATTS_TAG, "update connetion params status = %d, min_int = %d, max_int = %d,conn_int = %d,latency = %d, timeout = %d",
|
||||
param->update_conn_params.status,
|
||||
param->update_conn_params.min_int,
|
||||
param->update_conn_params.max_int,
|
||||
param->update_conn_params.conn_int,
|
||||
param->update_conn_params.latency,
|
||||
param->update_conn_params.timeout);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void example_write_event_env(esp_gatt_if_t gatts_if, prepare_type_env_t *prepare_write_env, esp_ble_gatts_cb_param_t *param){
|
||||
esp_gatt_status_t status = ESP_GATT_OK;
|
||||
if (param->write.need_rsp) {
|
||||
if (param->write.is_prep) {
|
||||
if (prepare_write_env->prepare_buf == NULL) {
|
||||
prepare_write_env->prepare_buf = (uint8_t *)malloc(PREPARE_BUF_MAX_SIZE * sizeof(uint8_t));
|
||||
prepare_write_env->prepare_len = 0;
|
||||
if (prepare_write_env->prepare_buf == NULL) {
|
||||
ESP_LOGE(GATTS_TAG, "Gatt_server prep no mem\n");
|
||||
status = ESP_GATT_NO_RESOURCES;
|
||||
}
|
||||
} else {
|
||||
if(param->write.offset > PREPARE_BUF_MAX_SIZE ||
|
||||
prepare_write_env->prepare_len > param->write.offset) {
|
||||
status = ESP_GATT_INVALID_OFFSET;
|
||||
} else if ((param->write.offset + param->write.len) > PREPARE_BUF_MAX_SIZE) {
|
||||
status = ESP_GATT_INVALID_ATTR_LEN;
|
||||
}
|
||||
}
|
||||
|
||||
esp_gatt_rsp_t *gatt_rsp = (esp_gatt_rsp_t *)malloc(sizeof(esp_gatt_rsp_t));
|
||||
gatt_rsp->attr_value.len = param->write.len;
|
||||
gatt_rsp->attr_value.handle = param->write.handle;
|
||||
gatt_rsp->attr_value.offset = param->write.offset;
|
||||
gatt_rsp->attr_value.auth_req = ESP_GATT_AUTH_REQ_NONE;
|
||||
memcpy(gatt_rsp->attr_value.value, param->write.value, param->write.len);
|
||||
esp_err_t response_err = esp_ble_gatts_send_response(gatts_if, param->write.conn_id, param->write.trans_id, status, gatt_rsp);
|
||||
|
||||
if (response_err != ESP_OK) {
|
||||
ESP_LOGE(GATTS_TAG, "Send response error\n");
|
||||
}
|
||||
free(gatt_rsp);
|
||||
if (status != ESP_GATT_OK) {
|
||||
return;
|
||||
}
|
||||
memcpy(prepare_write_env->prepare_buf + param->write.offset,
|
||||
param->write.value,
|
||||
param->write.len);
|
||||
prepare_write_env->prepare_len += param->write.len;
|
||||
|
||||
}else {
|
||||
esp_ble_gatts_send_response(gatts_if, param->write.conn_id, param->write.trans_id, status, NULL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void example_exec_write_event_env(prepare_type_env_t *prepare_write_env, esp_ble_gatts_cb_param_t *param){
|
||||
if (param->exec_write.exec_write_flag == ESP_GATT_PREP_WRITE_EXEC){
|
||||
esp_log_buffer_hex(GATTS_TAG, prepare_write_env->prepare_buf, prepare_write_env->prepare_len);
|
||||
}else{
|
||||
ESP_LOGI(GATTS_TAG,"ESP_GATT_PREP_WRITE_CANCEL");
|
||||
}
|
||||
if (prepare_write_env->prepare_buf) {
|
||||
free(prepare_write_env->prepare_buf);
|
||||
prepare_write_env->prepare_buf = NULL;
|
||||
}
|
||||
prepare_write_env->prepare_len = 0;
|
||||
}
|
||||
|
||||
static void gatts_profile_a_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param) {
|
||||
switch (event) {
|
||||
case ESP_GATTS_REG_EVT:
|
||||
ESP_LOGI(GATTS_TAG, "REGISTER_APP_EVT, status %d, app_id %d\n", param->reg.status, param->reg.app_id);
|
||||
gl_profile_tab[PROFILE_A_APP_ID].service_id.is_primary = true;
|
||||
gl_profile_tab[PROFILE_A_APP_ID].service_id.id.inst_id = 0x00;
|
||||
gl_profile_tab[PROFILE_A_APP_ID].service_id.id.uuid.len = ESP_UUID_LEN_16;
|
||||
gl_profile_tab[PROFILE_A_APP_ID].service_id.id.uuid.uuid.uuid16 = GATTS_SERVICE_UUID_TEST_A;
|
||||
gl_profile_tab[PROFILE_A_APP_ID].gatts_if = gatts_if;
|
||||
esp_err_t set_dev_name_ret = esp_ble_gap_set_device_name(TEST_DEVICE_NAME);
|
||||
if (set_dev_name_ret){
|
||||
ESP_LOGE(GATTS_TAG, "set device name failed, error code = %x", set_dev_name_ret);
|
||||
}
|
||||
#ifdef CONFIG_EXAMPLE_SET_RAW_ADV_DATA
|
||||
esp_err_t raw_adv_ret = esp_ble_gap_config_adv_data_raw(raw_adv_data, sizeof(raw_adv_data));
|
||||
if (raw_adv_ret){
|
||||
ESP_LOGE(GATTS_TAG, "config raw adv data failed, error code = %x ", raw_adv_ret);
|
||||
}
|
||||
adv_config_done |= adv_config_flag;
|
||||
esp_err_t raw_scan_ret = esp_ble_gap_config_scan_rsp_data_raw(raw_scan_rsp_data, sizeof(raw_scan_rsp_data));
|
||||
if (raw_scan_ret){
|
||||
ESP_LOGE(GATTS_TAG, "config raw scan rsp data failed, error code = %x", raw_scan_ret);
|
||||
}
|
||||
adv_config_done |= scan_rsp_config_flag;
|
||||
#else
|
||||
//config adv data
|
||||
esp_err_t ret = esp_ble_gap_config_adv_data(&adv_data);
|
||||
if (ret){
|
||||
ESP_LOGE(GATTS_TAG, "config adv data failed, error code = %x", ret);
|
||||
}
|
||||
adv_config_done |= adv_config_flag;
|
||||
//config scan response data
|
||||
ret = esp_ble_gap_config_adv_data(&scan_rsp_data);
|
||||
if (ret){
|
||||
ESP_LOGE(GATTS_TAG, "config scan response data failed, error code = %x", ret);
|
||||
}
|
||||
adv_config_done |= scan_rsp_config_flag;
|
||||
|
||||
#endif
|
||||
esp_ble_gatts_create_service(gatts_if, &gl_profile_tab[PROFILE_A_APP_ID].service_id, GATTS_NUM_HANDLE_TEST_A);
|
||||
break;
|
||||
case ESP_GATTS_READ_EVT: {
|
||||
ESP_LOGI(GATTS_TAG, "GATT_READ_EVT, conn_id %d, trans_id %d, handle %d\n", param->read.conn_id, param->read.trans_id, param->read.handle);
|
||||
esp_gatt_rsp_t rsp;
|
||||
memset(&rsp, 0, sizeof(esp_gatt_rsp_t));
|
||||
rsp.attr_value.handle = param->read.handle;
|
||||
rsp.attr_value.len = 4;
|
||||
rsp.attr_value.value[0] = 0xde;
|
||||
rsp.attr_value.value[1] = 0xed;
|
||||
rsp.attr_value.value[2] = 0xbe;
|
||||
rsp.attr_value.value[3] = 0xef;
|
||||
esp_ble_gatts_send_response(gatts_if, param->read.conn_id, param->read.trans_id,
|
||||
ESP_GATT_OK, &rsp);
|
||||
break;
|
||||
}
|
||||
case ESP_GATTS_WRITE_EVT: {
|
||||
#if (CONFIG_EXAMPLE_GATTS_NOTIFY_THROUGHPUT)
|
||||
ESP_LOGI(GATTS_TAG, "GATT_WRITE_EVT, conn_id %d, trans_id %d, handle %d", param->write.conn_id, param->write.trans_id, param->write.handle);
|
||||
if (!param->write.is_prep){
|
||||
ESP_LOGI(GATTS_TAG, "GATT_WRITE_EVT, value len %d, value :", param->write.len);
|
||||
esp_log_buffer_hex(GATTS_TAG, param->write.value, param->write.len);
|
||||
if (gl_profile_tab[PROFILE_A_APP_ID].descr_handle == param->write.handle && param->write.len == 2){
|
||||
uint16_t descr_value = param->write.value[1]<<8 | param->write.value[0];
|
||||
if (descr_value == 0x0001){
|
||||
if (a_property & ESP_GATT_CHAR_PROP_BIT_NOTIFY){
|
||||
|
||||
ESP_LOGI(GATTS_TAG, "notify enable");
|
||||
can_send_notify = true;
|
||||
xSemaphoreGive(gatts_semaphore);
|
||||
}
|
||||
}else if (descr_value == 0x0002){
|
||||
if (a_property & ESP_GATT_CHAR_PROP_BIT_INDICATE){
|
||||
ESP_LOGI(GATTS_TAG, "indicate enable");
|
||||
uint8_t indicate_data[600];
|
||||
for (int i = 0; i < sizeof(indicate_data); ++i)
|
||||
{
|
||||
indicate_data[i] = i%0xff;
|
||||
}
|
||||
|
||||
for (int j = 0; j < 1000; j++) {
|
||||
//the size of indicate_data[] need less than MTU size
|
||||
esp_ble_gatts_send_indicate(gatts_if, param->write.conn_id, gl_profile_tab[PROFILE_A_APP_ID].char_handle,
|
||||
sizeof(indicate_data), indicate_data, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (descr_value == 0x0000){
|
||||
can_send_notify = false;
|
||||
a_property = 0;
|
||||
ESP_LOGI(GATTS_TAG, "notify/indicate disable ");
|
||||
}else{
|
||||
ESP_LOGE(GATTS_TAG, "unknown descr value");
|
||||
esp_log_buffer_hex(GATTS_TAG, param->write.value, param->write.len);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
#endif /* #if (CONFIG_EXAMPLE_GATTS_NOTIFY_THROUGHPUT) */
|
||||
example_write_event_env(gatts_if, &a_prepare_write_env, param);
|
||||
#if (CONFIG_EXAMPLE_GATTC_WRITE_THROUGHPUT)
|
||||
if (param->write.handle == gl_profile_tab[PROFILE_A_APP_ID].char_handle) {
|
||||
// The last value byte is the checksum data, should used to check the data is received corrected or not.
|
||||
if (param->write.value[param->write.len - 1] ==
|
||||
check_sum(param->write.value, param->write.len - 1)) {
|
||||
write_len += param->write.len;
|
||||
}
|
||||
|
||||
if (start == false) {
|
||||
start_time = esp_timer_get_time();
|
||||
start = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif /* #if (CONFIG_EXAMPLE_GATTC_WRITE_THROUGHPUT) */
|
||||
|
||||
break;
|
||||
}
|
||||
case ESP_GATTS_EXEC_WRITE_EVT:
|
||||
ESP_LOGI(GATTS_TAG,"ESP_GATTS_EXEC_WRITE_EVT");
|
||||
#if (CONFIG_EXAMPLE_GATTC_WRITE_THROUGHPUT)
|
||||
if (param->exec_write.exec_write_flag == ESP_GATT_PREP_WRITE_CANCEL) {
|
||||
if (write_len > a_prepare_write_env.prepare_len) {
|
||||
write_len -= a_prepare_write_env.prepare_len;
|
||||
} else {
|
||||
write_len = 0;
|
||||
}
|
||||
}
|
||||
#endif /* #if (CONFIG_EXAMPLE_GATTC_WRITE_THROUGHPUT) */
|
||||
esp_ble_gatts_send_response(gatts_if, param->write.conn_id, param->write.trans_id, ESP_GATT_OK, NULL);
|
||||
example_exec_write_event_env(&a_prepare_write_env, param);
|
||||
break;
|
||||
case ESP_GATTS_MTU_EVT:
|
||||
ESP_LOGI(GATTS_TAG, "ESP_GATTS_MTU_EVT, MTU %d", param->mtu.mtu);
|
||||
break;
|
||||
case ESP_GATTS_UNREG_EVT:
|
||||
break;
|
||||
case ESP_GATTS_CREATE_EVT:
|
||||
ESP_LOGI(GATTS_TAG, "CREATE_SERVICE_EVT, status %d, service_handle %d\n", param->create.status, param->create.service_handle);
|
||||
gl_profile_tab[PROFILE_A_APP_ID].service_handle = param->create.service_handle;
|
||||
gl_profile_tab[PROFILE_A_APP_ID].char_uuid.len = ESP_UUID_LEN_16;
|
||||
gl_profile_tab[PROFILE_A_APP_ID].char_uuid.uuid.uuid16 = GATTS_CHAR_UUID_TEST_A;
|
||||
|
||||
esp_ble_gatts_start_service(gl_profile_tab[PROFILE_A_APP_ID].service_handle);
|
||||
a_property = ESP_GATT_CHAR_PROP_BIT_READ | ESP_GATT_CHAR_PROP_BIT_WRITE | ESP_GATT_CHAR_PROP_BIT_NOTIFY;
|
||||
esp_err_t add_char_ret = esp_ble_gatts_add_char(gl_profile_tab[PROFILE_A_APP_ID].service_handle, &gl_profile_tab[PROFILE_A_APP_ID].char_uuid,
|
||||
ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE,
|
||||
a_property,
|
||||
&gatts_demo_char1_val, NULL);
|
||||
if (add_char_ret){
|
||||
ESP_LOGE(GATTS_TAG, "add char failed, error code =%x",add_char_ret);
|
||||
}
|
||||
break;
|
||||
case ESP_GATTS_ADD_INCL_SRVC_EVT:
|
||||
break;
|
||||
case ESP_GATTS_ADD_CHAR_EVT: {
|
||||
uint16_t length = 0;
|
||||
const uint8_t *prf_char;
|
||||
|
||||
ESP_LOGI(GATTS_TAG, "ADD_CHAR_EVT, status %d, attr_handle %d, service_handle %d\n",
|
||||
param->add_char.status, param->add_char.attr_handle, param->add_char.service_handle);
|
||||
gl_profile_tab[PROFILE_A_APP_ID].char_handle = param->add_char.attr_handle;
|
||||
gl_profile_tab[PROFILE_A_APP_ID].descr_uuid.len = ESP_UUID_LEN_16;
|
||||
gl_profile_tab[PROFILE_A_APP_ID].descr_uuid.uuid.uuid16 = ESP_GATT_UUID_CHAR_CLIENT_CONFIG;
|
||||
esp_err_t get_attr_ret = esp_ble_gatts_get_attr_value(param->add_char.attr_handle, &length, &prf_char);
|
||||
if (get_attr_ret == ESP_FAIL){
|
||||
ESP_LOGE(GATTS_TAG, "ILLEGAL HANDLE");
|
||||
}
|
||||
|
||||
ESP_LOGI(GATTS_TAG, "the gatts demo char length = %x\n", length);
|
||||
for(int i = 0; i < length; i++){
|
||||
ESP_LOGI(GATTS_TAG, "prf_char[%x] =%x\n",i,prf_char[i]);
|
||||
}
|
||||
esp_err_t add_descr_ret = esp_ble_gatts_add_char_descr(gl_profile_tab[PROFILE_A_APP_ID].service_handle, &gl_profile_tab[PROFILE_A_APP_ID].descr_uuid,
|
||||
ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE, NULL, NULL);
|
||||
if (add_descr_ret){
|
||||
ESP_LOGE(GATTS_TAG, "add char descr failed, error code =%x", add_descr_ret);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ESP_GATTS_ADD_CHAR_DESCR_EVT:
|
||||
|
||||
gl_profile_tab[PROFILE_A_APP_ID].descr_handle = param->add_char_descr.attr_handle;
|
||||
ESP_LOGI(GATTS_TAG, "ADD_DESCR_EVT, status %d, attr_handle %d, service_handle %d\n",
|
||||
param->add_char_descr.status, param->add_char_descr.attr_handle, param->add_char_descr.service_handle);
|
||||
break;
|
||||
case ESP_GATTS_DELETE_EVT:
|
||||
break;
|
||||
case ESP_GATTS_START_EVT:
|
||||
ESP_LOGI(GATTS_TAG, "SERVICE_START_EVT, status %d, service_handle %d\n",
|
||||
param->start.status, param->start.service_handle);
|
||||
break;
|
||||
case ESP_GATTS_STOP_EVT:
|
||||
break;
|
||||
case ESP_GATTS_CONNECT_EVT: {
|
||||
is_connect = true;
|
||||
esp_ble_conn_update_params_t conn_params = {0};
|
||||
memcpy(conn_params.bda, param->connect.remote_bda, sizeof(esp_bd_addr_t));
|
||||
/* For the IOS system, please reference the apple official documents about the ble connection parameters restrictions. */
|
||||
conn_params.latency = 0;
|
||||
conn_params.max_int = 0x20; // max_int = 0x20*1.25ms = 40ms
|
||||
conn_params.min_int = 0x10; // min_int = 0x10*1.25ms = 20ms
|
||||
conn_params.timeout = 400; // timeout = 400*10ms = 4000ms
|
||||
ESP_LOGI(GATTS_TAG, "ESP_GATTS_CONNECT_EVT, conn_id %d, remote %02x:%02x:%02x:%02x:%02x:%02x:",
|
||||
param->connect.conn_id,
|
||||
param->connect.remote_bda[0], param->connect.remote_bda[1], param->connect.remote_bda[2],
|
||||
param->connect.remote_bda[3], param->connect.remote_bda[4], param->connect.remote_bda[5]);
|
||||
gl_profile_tab[PROFILE_A_APP_ID].conn_id = param->connect.conn_id;
|
||||
//start sent the update connection parameters to the peer device.
|
||||
//esp_ble_gap_update_conn_params(&conn_params);
|
||||
break;
|
||||
}
|
||||
case ESP_GATTS_DISCONNECT_EVT:
|
||||
is_connect = false;
|
||||
ESP_LOGI(GATTS_TAG, "ESP_GATTS_DISCONNECT_EVT");
|
||||
esp_ble_gap_start_advertising(&adv_params);
|
||||
break;
|
||||
case ESP_GATTS_CONF_EVT:
|
||||
ESP_LOGI(GATTS_TAG, "ESP_GATTS_CONF_EVT, status %d", param->conf.status);
|
||||
#if (CONFIG_EXAMPLE_GATTC_WRITE_THROUGHPUT)
|
||||
start_time = false;
|
||||
current_time = 0;
|
||||
write_len = 0;
|
||||
#endif /* #if (CONFIG_EXAMPLE_GATTC_WRITE_THROUGHPUT) */
|
||||
break;
|
||||
case ESP_GATTS_OPEN_EVT:
|
||||
case ESP_GATTS_CANCEL_OPEN_EVT:
|
||||
case ESP_GATTS_CLOSE_EVT:
|
||||
case ESP_GATTS_LISTEN_EVT:
|
||||
break;
|
||||
case ESP_GATTS_CONGEST_EVT:
|
||||
#if (CONFIG_EXAMPLE_GATTS_NOTIFY_THROUGHPUT)
|
||||
if (param->congest.congested) {
|
||||
can_send_notify = false;
|
||||
} else {
|
||||
can_send_notify = true;
|
||||
xSemaphoreGive(gatts_semaphore);
|
||||
}
|
||||
#endif /* #if (CONFIG_EXAMPLE_GATTS_NOTIFY_THROUGHPUT) */
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param)
|
||||
{
|
||||
/* If event is register event, store the gatts_if for each profile */
|
||||
if (event == ESP_GATTS_REG_EVT) {
|
||||
if (param->reg.status == ESP_GATT_OK) {
|
||||
gl_profile_tab[param->reg.app_id].gatts_if = gatts_if;
|
||||
} else {
|
||||
ESP_LOGI(GATTS_TAG, "Reg app failed, app_id %04x, status %d\n",
|
||||
param->reg.app_id,
|
||||
param->reg.status);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* If the gatts_if equal to profile A, call profile A cb handler,
|
||||
* so here call each profile's callback */
|
||||
do {
|
||||
int idx;
|
||||
for (idx = 0; idx < PROFILE_NUM; idx++) {
|
||||
if (gatts_if == ESP_GATT_IF_NONE || /* ESP_GATT_IF_NONE, not specify a certain gatt_if, need to call every profile cb function */
|
||||
gatts_if == gl_profile_tab[idx].gatts_if) {
|
||||
if (gl_profile_tab[idx].gatts_cb) {
|
||||
gl_profile_tab[idx].gatts_cb(event, gatts_if, param);
|
||||
}
|
||||
}
|
||||
}
|
||||
} while (0);
|
||||
}
|
||||
|
||||
void throughput_server_task(void *param)
|
||||
{
|
||||
vTaskDelay(2000 / portTICK_PERIOD_MS);
|
||||
#if (CONFIG_EXAMPLE_GATTS_NOTIFY_THROUGHPUT)
|
||||
uint8_t sum = check_sum(indicate_data, sizeof(indicate_data) - 1);
|
||||
// Added the check sum in the last data value.
|
||||
indicate_data[GATTS_NOTIFY_LEN - 1] = sum;
|
||||
#endif /* #if (CONFIG_EXAMPLE_GATTS_NOTIFY_THROUGHPUT) */
|
||||
|
||||
while(1) {
|
||||
#if (CONFIG_EXAMPLE_GATTS_NOTIFY_THROUGHPUT)
|
||||
if (!can_send_notify) {
|
||||
int res = xSemaphoreTake(gatts_semaphore, portMAX_DELAY);
|
||||
assert(res == pdTRUE);
|
||||
} else {
|
||||
if (is_connect) {
|
||||
int free_buff_num = esp_ble_get_cur_sendable_packets_num(gl_profile_tab[PROFILE_A_APP_ID].conn_id);
|
||||
if(free_buff_num > 0) {
|
||||
for( ; free_buff_num > 0; free_buff_num--) {
|
||||
esp_ble_gatts_send_indicate(gl_profile_tab[PROFILE_A_APP_ID].gatts_if, gl_profile_tab[PROFILE_A_APP_ID].conn_id,
|
||||
gl_profile_tab[PROFILE_A_APP_ID].char_handle,
|
||||
sizeof(indicate_data), indicate_data, false);
|
||||
}
|
||||
} else { //Add the vTaskDelay to prevent this task from consuming the CPU all the time, causing low-priority tasks to not be executed at all.
|
||||
vTaskDelay( 10 / portTICK_PERIOD_MS );
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif /* #if (CONFIG_EXAMPLE_GATTS_NOTIFY_THROUGHPUT) */
|
||||
|
||||
#if (CONFIG_EXAMPLE_GATTC_WRITE_THROUGHPUT)
|
||||
uint32_t bit_rate = 0;
|
||||
vTaskDelay(2000 / portTICK_PERIOD_MS);
|
||||
if (start_time) {
|
||||
current_time = esp_timer_get_time();
|
||||
bit_rate = write_len * SECOND_TO_USECOND / (current_time - start_time);
|
||||
ESP_LOGI(GATTS_TAG, "GATTC write Bit rate = %d Byte/s, = %d bit/s, time = %ds",
|
||||
bit_rate, bit_rate<<3, (int)((current_time - start_time) / SECOND_TO_USECOND));
|
||||
} else {
|
||||
ESP_LOGI(GATTS_TAG, "GATTC write Bit rate = 0 Byte/s, = 0 bit/s");
|
||||
}
|
||||
#endif /* #if (CONFIG_EXAMPLE_GATTC_WRITE_THROUGHPUT) */
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
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_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT));
|
||||
|
||||
esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
|
||||
ret = esp_bt_controller_init(&bt_cfg);
|
||||
if (ret) {
|
||||
ESP_LOGE(GATTS_TAG, "%s initialize controller failed\n", __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
ret = esp_bt_controller_enable(ESP_BT_MODE_BLE);
|
||||
if (ret) {
|
||||
ESP_LOGE(GATTS_TAG, "%s enable controller failed\n", __func__);
|
||||
return;
|
||||
}
|
||||
ret = esp_bluedroid_init();
|
||||
if (ret) {
|
||||
ESP_LOGE(GATTS_TAG, "%s init bluetooth failed\n", __func__);
|
||||
return;
|
||||
}
|
||||
ret = esp_bluedroid_enable();
|
||||
if (ret) {
|
||||
ESP_LOGE(GATTS_TAG, "%s enable bluetooth failed\n", __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
ret = esp_ble_gatts_register_callback(gatts_event_handler);
|
||||
if (ret){
|
||||
ESP_LOGE(GATTS_TAG, "gatts register error, error code = %x", ret);
|
||||
return;
|
||||
}
|
||||
ret = esp_ble_gap_register_callback(gap_event_handler);
|
||||
if (ret){
|
||||
ESP_LOGE(GATTS_TAG, "gap register error, error code = %x", ret);
|
||||
return;
|
||||
}
|
||||
ret = esp_ble_gatts_app_register(PROFILE_A_APP_ID);
|
||||
if (ret){
|
||||
ESP_LOGE(GATTS_TAG, "gatts app register error, error code = %x", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
esp_err_t local_mtu_ret = esp_ble_gatt_set_local_mtu(517);
|
||||
if (local_mtu_ret){
|
||||
ESP_LOGE(GATTS_TAG, "set local MTU failed, error code = %x", local_mtu_ret);
|
||||
}
|
||||
// The task is only created on the CPU core that Bluetooth is working on,
|
||||
// preventing the sending task from using the un-updated Bluetooth state on another CPU.
|
||||
xTaskCreatePinnedToCore(&throughput_server_task, "throughput_server_task", 4096, NULL, 15, NULL, BLUETOOTH_TASK_PINNED_TO_CORE);
|
||||
#if (CONFIG_EXAMPLE_GATTS_NOTIFY_THROUGHPUT)
|
||||
gatts_semaphore = xSemaphoreCreateBinary();
|
||||
if (!gatts_semaphore) {
|
||||
ESP_LOGE(GATTS_TAG, "%s, init fail, the gatts semaphore create fail.", __func__);
|
||||
return;
|
||||
}
|
||||
#endif /* #if (CONFIG_EXAMPLE_GATTS_NOTIFY_THROUGHPUT) */
|
||||
return;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
# Override some defaults so BT stack is enabled
|
||||
# by default in this example
|
||||
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_BTDM_CTRL_BLE_MAX_CONN=9
|
||||
CONFIG_EXAMPLE_GATTS_NOTIFY_THROUGHPUT=y
|
||||
CONFIG_BTDM_MODEM_SLEEP=n
|
||||
CONFIG_BTDM_CTRL_PINNED_TO_CORE_1=y
|
||||
CONFIG_BTDM_CTRL_PINNED_TO_CORE=1
|
||||
#
|
||||
# Serial flasher config
|
||||
#
|
||||
CONFIG_ESPTOOLPY_MONITOR_BAUD_921600B=y
|
||||
CONFIG_ESPTOOLPY_MONITOR_BAUD=921600
|
||||
#
|
||||
# ESP32-specific
|
||||
#
|
||||
CONFIG_ESP32_TRACEMEM_RESERVE_DRAM=0x0
|
||||
CONFIG_ESP32_ENABLE_COREDUMP_TO_NONE=y
|
||||
CONFIG_ESP_CONSOLE_UART_BAUDRATE=921600
|
||||
@@ -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(blufi_demo)
|
||||
@@ -0,0 +1,10 @@
|
||||
#
|
||||
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
|
||||
# project subdirectory.
|
||||
#
|
||||
|
||||
PROJECT_NAME := blufi_demo
|
||||
|
||||
COMPONENT_ADD_INCLUDEDIRS := components/include
|
||||
|
||||
include $(IDF_PATH)/make/project.mk
|
||||
@@ -0,0 +1,21 @@
|
||||
| Supported Targets | ESP32 |
|
||||
| ----------------- | ----- |
|
||||
|
||||
ESP-IDF Blufi demo
|
||||
=======================
|
||||
|
||||
This is the demo for bluetooth config wifi connection to ap.
|
||||
|
||||
To test this demo, you need to prepare a mobile phone with blufi application installed. You can download the blufi application from [Android version](https://github.com/EspressifApp/EspBlufi) and [iOS version](https://itunes.apple.com/cn/app/espblufi/id1450614082?mt=8).
|
||||
|
||||
Blufi is completely open source, here is the download link:
|
||||
|
||||
* [blufi source code](https://github.com/espressif/esp-idf/tree/master/examples/bluetooth/bluedroid/ble/blufi)
|
||||
|
||||
* [BluFi protocol](https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/blufi.html?highlight=blufi#the-frame-formats-defined-in-blufi)
|
||||
|
||||
* [iOS source code](https://github.com/EspressifApp/EspBlufiForiOS)
|
||||
|
||||
* [Android source code](https://github.com/EspressifApp/EspBlufi)
|
||||
|
||||
* [Bluetooth Network User Guide CN](https://www.espressif.com/sites/default/files/documentation/esp32_bluetooth_networking_user_guide_cn.pdf)
|
||||
@@ -0,0 +1,3 @@
|
||||
idf_component_register(SRCS "blufi_example_main.c"
|
||||
"blufi_security.c"
|
||||
INCLUDE_DIRS ".")
|
||||
@@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#define BLUFI_EXAMPLE_TAG "BLUFI_EXAMPLE"
|
||||
#define BLUFI_INFO(fmt, ...) ESP_LOGI(BLUFI_EXAMPLE_TAG, fmt, ##__VA_ARGS__)
|
||||
#define BLUFI_ERROR(fmt, ...) ESP_LOGE(BLUFI_EXAMPLE_TAG, fmt, ##__VA_ARGS__)
|
||||
|
||||
void blufi_dh_negotiate_data_handler(uint8_t *data, int len, uint8_t **output_data, int *output_len, bool *need_free);
|
||||
int blufi_aes_encrypt(uint8_t iv8, uint8_t *crypt_data, int crypt_len);
|
||||
int blufi_aes_decrypt(uint8_t iv8, uint8_t *crypt_data, int crypt_len);
|
||||
uint16_t blufi_crc_checksum(uint8_t iv8, uint8_t *data, int len);
|
||||
|
||||
int blufi_security_init(void);
|
||||
void blufi_security_deinit(void);
|
||||
@@ -0,0 +1,472 @@
|
||||
/*
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
|
||||
/****************************************************************************
|
||||
* This is a demo for bluetooth config wifi connection to ap. You can config ESP32 to connect a softap
|
||||
* or config ESP32 as a softap to be connected by other device. APP can be downloaded from github
|
||||
* android source code: https://github.com/EspressifApp/EspBlufi
|
||||
* iOS source code: https://github.com/EspressifApp/EspBlufiForiOS
|
||||
****************************************************************************/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/event_groups.h"
|
||||
#include "esp_system.h"
|
||||
#include "esp_wifi.h"
|
||||
#include "esp_event.h"
|
||||
#include "esp_log.h"
|
||||
#include "nvs_flash.h"
|
||||
#include "esp_bt.h"
|
||||
|
||||
#include "esp_blufi_api.h"
|
||||
#include "esp_bt_defs.h"
|
||||
#include "esp_gap_ble_api.h"
|
||||
#include "esp_bt_main.h"
|
||||
#include "esp_bt_device.h"
|
||||
#include "blufi_example.h"
|
||||
|
||||
static void example_event_callback(esp_blufi_cb_event_t event, esp_blufi_cb_param_t *param);
|
||||
|
||||
#define BLUFI_DEVICE_NAME "BLUFI_DEVICE"
|
||||
static uint8_t example_service_uuid128[32] = {
|
||||
/* LSB <--------------------------------------------------------------------------------> MSB */
|
||||
//first uuid, 16bit, [12],[13] is the value
|
||||
0xfb, 0x34, 0x9b, 0x5f, 0x80, 0x00, 0x00, 0x80, 0x00, 0x10, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00,
|
||||
};
|
||||
|
||||
//static uint8_t test_manufacturer[TEST_MANUFACTURER_DATA_LEN] = {0x12, 0x23, 0x45, 0x56};
|
||||
static esp_ble_adv_data_t example_adv_data = {
|
||||
.set_scan_rsp = false,
|
||||
.include_name = true,
|
||||
.include_txpower = true,
|
||||
.min_interval = 0x0006, //slave connection min interval, Time = min_interval * 1.25 msec
|
||||
.max_interval = 0x0010, //slave connection max interval, Time = max_interval * 1.25 msec
|
||||
.appearance = 0x00,
|
||||
.manufacturer_len = 0,
|
||||
.p_manufacturer_data = NULL,
|
||||
.service_data_len = 0,
|
||||
.p_service_data = NULL,
|
||||
.service_uuid_len = 16,
|
||||
.p_service_uuid = example_service_uuid128,
|
||||
.flag = 0x6,
|
||||
};
|
||||
|
||||
static esp_ble_adv_params_t example_adv_params = {
|
||||
.adv_int_min = 0x100,
|
||||
.adv_int_max = 0x100,
|
||||
.adv_type = ADV_TYPE_IND,
|
||||
.own_addr_type = BLE_ADDR_TYPE_PUBLIC,
|
||||
//.peer_addr =
|
||||
//.peer_addr_type =
|
||||
.channel_map = ADV_CHNL_ALL,
|
||||
.adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY,
|
||||
};
|
||||
|
||||
#define WIFI_LIST_NUM 10
|
||||
|
||||
static wifi_config_t sta_config;
|
||||
static wifi_config_t ap_config;
|
||||
|
||||
/* FreeRTOS event group to signal when we are connected & ready to make a request */
|
||||
static EventGroupHandle_t wifi_event_group;
|
||||
|
||||
/* The event group allows multiple bits for each event,
|
||||
but we only care about one event - are we connected
|
||||
to the AP with an IP? */
|
||||
const int CONNECTED_BIT = BIT0;
|
||||
|
||||
/* store the station info for send back to phone */
|
||||
static bool gl_sta_connected = false;
|
||||
static bool ble_is_connected = false;
|
||||
static uint8_t gl_sta_bssid[6];
|
||||
static uint8_t gl_sta_ssid[32];
|
||||
static int gl_sta_ssid_len;
|
||||
|
||||
/* connect infor*/
|
||||
static uint8_t server_if;
|
||||
static uint16_t conn_id;
|
||||
|
||||
static void ip_event_handler(void* arg, esp_event_base_t event_base,
|
||||
int32_t event_id, void* event_data)
|
||||
{
|
||||
wifi_mode_t mode;
|
||||
|
||||
switch (event_id) {
|
||||
case IP_EVENT_STA_GOT_IP: {
|
||||
esp_blufi_extra_info_t info;
|
||||
|
||||
xEventGroupSetBits(wifi_event_group, CONNECTED_BIT);
|
||||
esp_wifi_get_mode(&mode);
|
||||
|
||||
memset(&info, 0, sizeof(esp_blufi_extra_info_t));
|
||||
memcpy(info.sta_bssid, gl_sta_bssid, 6);
|
||||
info.sta_bssid_set = true;
|
||||
info.sta_ssid = gl_sta_ssid;
|
||||
info.sta_ssid_len = gl_sta_ssid_len;
|
||||
if (ble_is_connected == true) {
|
||||
esp_blufi_send_wifi_conn_report(mode, ESP_BLUFI_STA_CONN_SUCCESS, 0, &info);
|
||||
} else {
|
||||
BLUFI_INFO("BLUFI BLE is not connected yet\n");
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
static void wifi_event_handler(void* arg, esp_event_base_t event_base,
|
||||
int32_t event_id, void* event_data)
|
||||
{
|
||||
wifi_event_sta_connected_t *event;
|
||||
wifi_mode_t mode;
|
||||
|
||||
switch (event_id) {
|
||||
case WIFI_EVENT_STA_START:
|
||||
esp_wifi_connect();
|
||||
break;
|
||||
case WIFI_EVENT_STA_CONNECTED:
|
||||
gl_sta_connected = true;
|
||||
event = (wifi_event_sta_connected_t*) event_data;
|
||||
memcpy(gl_sta_bssid, event->bssid, 6);
|
||||
memcpy(gl_sta_ssid, event->ssid, event->ssid_len);
|
||||
gl_sta_ssid_len = event->ssid_len;
|
||||
break;
|
||||
case WIFI_EVENT_STA_DISCONNECTED:
|
||||
/* This is a workaround as ESP32 WiFi libs don't currently
|
||||
auto-reassociate. */
|
||||
gl_sta_connected = false;
|
||||
memset(gl_sta_ssid, 0, 32);
|
||||
memset(gl_sta_bssid, 0, 6);
|
||||
gl_sta_ssid_len = 0;
|
||||
esp_wifi_connect();
|
||||
xEventGroupClearBits(wifi_event_group, CONNECTED_BIT);
|
||||
break;
|
||||
case WIFI_EVENT_AP_START:
|
||||
esp_wifi_get_mode(&mode);
|
||||
|
||||
/* TODO: get config or information of softap, then set to report extra_info */
|
||||
if (ble_is_connected == true) {
|
||||
if (gl_sta_connected) {
|
||||
esp_blufi_send_wifi_conn_report(mode, ESP_BLUFI_STA_CONN_SUCCESS, 0, NULL);
|
||||
} else {
|
||||
esp_blufi_send_wifi_conn_report(mode, ESP_BLUFI_STA_CONN_FAIL, 0, NULL);
|
||||
}
|
||||
} else {
|
||||
BLUFI_INFO("BLUFI BLE is not connected yet\n");
|
||||
}
|
||||
break;
|
||||
case WIFI_EVENT_SCAN_DONE: {
|
||||
uint16_t apCount = 0;
|
||||
esp_wifi_scan_get_ap_num(&apCount);
|
||||
if (apCount == 0) {
|
||||
BLUFI_INFO("Nothing AP found");
|
||||
break;
|
||||
}
|
||||
wifi_ap_record_t *ap_list = (wifi_ap_record_t *)malloc(sizeof(wifi_ap_record_t) * apCount);
|
||||
if (!ap_list) {
|
||||
BLUFI_ERROR("malloc error, ap_list is NULL");
|
||||
break;
|
||||
}
|
||||
ESP_ERROR_CHECK(esp_wifi_scan_get_ap_records(&apCount, ap_list));
|
||||
esp_blufi_ap_record_t * blufi_ap_list = (esp_blufi_ap_record_t *)malloc(apCount * sizeof(esp_blufi_ap_record_t));
|
||||
if (!blufi_ap_list) {
|
||||
if (ap_list) {
|
||||
free(ap_list);
|
||||
}
|
||||
BLUFI_ERROR("malloc error, blufi_ap_list is NULL");
|
||||
break;
|
||||
}
|
||||
for (int i = 0; i < apCount; ++i)
|
||||
{
|
||||
blufi_ap_list[i].rssi = ap_list[i].rssi;
|
||||
memcpy(blufi_ap_list[i].ssid, ap_list[i].ssid, sizeof(ap_list[i].ssid));
|
||||
}
|
||||
|
||||
if (ble_is_connected == true) {
|
||||
esp_blufi_send_wifi_list(apCount, blufi_ap_list);
|
||||
} else {
|
||||
BLUFI_INFO("BLUFI BLE is not connected yet\n");
|
||||
}
|
||||
|
||||
esp_wifi_scan_stop();
|
||||
free(ap_list);
|
||||
free(blufi_ap_list);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
static void initialise_wifi(void)
|
||||
{
|
||||
ESP_ERROR_CHECK(esp_netif_init());
|
||||
wifi_event_group = xEventGroupCreate();
|
||||
ESP_ERROR_CHECK(esp_event_loop_create_default());
|
||||
esp_netif_t *sta_netif = esp_netif_create_default_wifi_sta();
|
||||
assert(sta_netif);
|
||||
ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_event_handler, NULL));
|
||||
ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &ip_event_handler, NULL));
|
||||
|
||||
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
|
||||
ESP_ERROR_CHECK( esp_wifi_init(&cfg) );
|
||||
ESP_ERROR_CHECK( esp_wifi_set_mode(WIFI_MODE_STA) );
|
||||
ESP_ERROR_CHECK( esp_wifi_start() );
|
||||
}
|
||||
|
||||
static esp_blufi_callbacks_t example_callbacks = {
|
||||
.event_cb = example_event_callback,
|
||||
.negotiate_data_handler = blufi_dh_negotiate_data_handler,
|
||||
.encrypt_func = blufi_aes_encrypt,
|
||||
.decrypt_func = blufi_aes_decrypt,
|
||||
.checksum_func = blufi_crc_checksum,
|
||||
};
|
||||
|
||||
static void example_event_callback(esp_blufi_cb_event_t event, esp_blufi_cb_param_t *param)
|
||||
{
|
||||
/* actually, should post to blufi_task handle the procedure,
|
||||
* now, as a example, we do it more simply */
|
||||
switch (event) {
|
||||
case ESP_BLUFI_EVENT_INIT_FINISH:
|
||||
BLUFI_INFO("BLUFI init finish\n");
|
||||
|
||||
esp_ble_gap_set_device_name(BLUFI_DEVICE_NAME);
|
||||
esp_ble_gap_config_adv_data(&example_adv_data);
|
||||
break;
|
||||
case ESP_BLUFI_EVENT_DEINIT_FINISH:
|
||||
BLUFI_INFO("BLUFI deinit finish\n");
|
||||
break;
|
||||
case ESP_BLUFI_EVENT_BLE_CONNECT:
|
||||
BLUFI_INFO("BLUFI ble connect\n");
|
||||
ble_is_connected = true;
|
||||
server_if = param->connect.server_if;
|
||||
conn_id = param->connect.conn_id;
|
||||
esp_ble_gap_stop_advertising();
|
||||
blufi_security_init();
|
||||
break;
|
||||
case ESP_BLUFI_EVENT_BLE_DISCONNECT:
|
||||
BLUFI_INFO("BLUFI ble disconnect\n");
|
||||
ble_is_connected = false;
|
||||
blufi_security_deinit();
|
||||
esp_ble_gap_start_advertising(&example_adv_params);
|
||||
break;
|
||||
case ESP_BLUFI_EVENT_SET_WIFI_OPMODE:
|
||||
BLUFI_INFO("BLUFI Set WIFI opmode %d\n", param->wifi_mode.op_mode);
|
||||
ESP_ERROR_CHECK( esp_wifi_set_mode(param->wifi_mode.op_mode) );
|
||||
break;
|
||||
case ESP_BLUFI_EVENT_REQ_CONNECT_TO_AP:
|
||||
BLUFI_INFO("BLUFI requset wifi connect to AP\n");
|
||||
/* there is no wifi callback when the device has already connected to this wifi
|
||||
so disconnect wifi before connection.
|
||||
*/
|
||||
esp_wifi_disconnect();
|
||||
esp_wifi_connect();
|
||||
break;
|
||||
case ESP_BLUFI_EVENT_REQ_DISCONNECT_FROM_AP:
|
||||
BLUFI_INFO("BLUFI requset wifi disconnect from AP\n");
|
||||
esp_wifi_disconnect();
|
||||
break;
|
||||
case ESP_BLUFI_EVENT_REPORT_ERROR:
|
||||
BLUFI_ERROR("BLUFI report error, error code %d\n", param->report_error.state);
|
||||
esp_blufi_send_error_info(param->report_error.state);
|
||||
break;
|
||||
case ESP_BLUFI_EVENT_GET_WIFI_STATUS: {
|
||||
wifi_mode_t mode;
|
||||
esp_blufi_extra_info_t info;
|
||||
|
||||
esp_wifi_get_mode(&mode);
|
||||
|
||||
if (gl_sta_connected) {
|
||||
memset(&info, 0, sizeof(esp_blufi_extra_info_t));
|
||||
memcpy(info.sta_bssid, gl_sta_bssid, 6);
|
||||
info.sta_bssid_set = true;
|
||||
info.sta_ssid = gl_sta_ssid;
|
||||
info.sta_ssid_len = gl_sta_ssid_len;
|
||||
esp_blufi_send_wifi_conn_report(mode, ESP_BLUFI_STA_CONN_SUCCESS, 0, &info);
|
||||
} else {
|
||||
esp_blufi_send_wifi_conn_report(mode, ESP_BLUFI_STA_CONN_FAIL, 0, NULL);
|
||||
}
|
||||
BLUFI_INFO("BLUFI get wifi status from AP\n");
|
||||
|
||||
break;
|
||||
}
|
||||
case ESP_BLUFI_EVENT_RECV_SLAVE_DISCONNECT_BLE:
|
||||
BLUFI_INFO("blufi close a gatt connection");
|
||||
esp_blufi_close(server_if, conn_id);
|
||||
break;
|
||||
case ESP_BLUFI_EVENT_DEAUTHENTICATE_STA:
|
||||
/* TODO */
|
||||
break;
|
||||
case ESP_BLUFI_EVENT_RECV_STA_BSSID:
|
||||
memcpy(sta_config.sta.bssid, param->sta_bssid.bssid, 6);
|
||||
sta_config.sta.bssid_set = 1;
|
||||
esp_wifi_set_config(WIFI_IF_STA, &sta_config);
|
||||
BLUFI_INFO("Recv STA BSSID %s\n", sta_config.sta.ssid);
|
||||
break;
|
||||
case ESP_BLUFI_EVENT_RECV_STA_SSID:
|
||||
strncpy((char *)sta_config.sta.ssid, (char *)param->sta_ssid.ssid, param->sta_ssid.ssid_len);
|
||||
sta_config.sta.ssid[param->sta_ssid.ssid_len] = '\0';
|
||||
esp_wifi_set_config(WIFI_IF_STA, &sta_config);
|
||||
BLUFI_INFO("Recv STA SSID %s\n", sta_config.sta.ssid);
|
||||
break;
|
||||
case ESP_BLUFI_EVENT_RECV_STA_PASSWD:
|
||||
strncpy((char *)sta_config.sta.password, (char *)param->sta_passwd.passwd, param->sta_passwd.passwd_len);
|
||||
sta_config.sta.password[param->sta_passwd.passwd_len] = '\0';
|
||||
esp_wifi_set_config(WIFI_IF_STA, &sta_config);
|
||||
BLUFI_INFO("Recv STA PASSWORD %s\n", sta_config.sta.password);
|
||||
break;
|
||||
case ESP_BLUFI_EVENT_RECV_SOFTAP_SSID:
|
||||
strncpy((char *)ap_config.ap.ssid, (char *)param->softap_ssid.ssid, param->softap_ssid.ssid_len);
|
||||
ap_config.ap.ssid[param->softap_ssid.ssid_len] = '\0';
|
||||
ap_config.ap.ssid_len = param->softap_ssid.ssid_len;
|
||||
esp_wifi_set_config(WIFI_IF_AP, &ap_config);
|
||||
BLUFI_INFO("Recv SOFTAP SSID %s, ssid len %d\n", ap_config.ap.ssid, ap_config.ap.ssid_len);
|
||||
break;
|
||||
case ESP_BLUFI_EVENT_RECV_SOFTAP_PASSWD:
|
||||
strncpy((char *)ap_config.ap.password, (char *)param->softap_passwd.passwd, param->softap_passwd.passwd_len);
|
||||
ap_config.ap.password[param->softap_passwd.passwd_len] = '\0';
|
||||
esp_wifi_set_config(WIFI_IF_AP, &ap_config);
|
||||
BLUFI_INFO("Recv SOFTAP PASSWORD %s len = %d\n", ap_config.ap.password, param->softap_passwd.passwd_len);
|
||||
break;
|
||||
case ESP_BLUFI_EVENT_RECV_SOFTAP_MAX_CONN_NUM:
|
||||
if (param->softap_max_conn_num.max_conn_num > 4) {
|
||||
return;
|
||||
}
|
||||
ap_config.ap.max_connection = param->softap_max_conn_num.max_conn_num;
|
||||
esp_wifi_set_config(WIFI_IF_AP, &ap_config);
|
||||
BLUFI_INFO("Recv SOFTAP MAX CONN NUM %d\n", ap_config.ap.max_connection);
|
||||
break;
|
||||
case ESP_BLUFI_EVENT_RECV_SOFTAP_AUTH_MODE:
|
||||
if (param->softap_auth_mode.auth_mode >= WIFI_AUTH_MAX) {
|
||||
return;
|
||||
}
|
||||
ap_config.ap.authmode = param->softap_auth_mode.auth_mode;
|
||||
esp_wifi_set_config(WIFI_IF_AP, &ap_config);
|
||||
BLUFI_INFO("Recv SOFTAP AUTH MODE %d\n", ap_config.ap.authmode);
|
||||
break;
|
||||
case ESP_BLUFI_EVENT_RECV_SOFTAP_CHANNEL:
|
||||
if (param->softap_channel.channel > 13) {
|
||||
return;
|
||||
}
|
||||
ap_config.ap.channel = param->softap_channel.channel;
|
||||
esp_wifi_set_config(WIFI_IF_AP, &ap_config);
|
||||
BLUFI_INFO("Recv SOFTAP CHANNEL %d\n", ap_config.ap.channel);
|
||||
break;
|
||||
case ESP_BLUFI_EVENT_GET_WIFI_LIST:{
|
||||
wifi_scan_config_t scanConf = {
|
||||
.ssid = NULL,
|
||||
.bssid = NULL,
|
||||
.channel = 0,
|
||||
.show_hidden = false
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_wifi_scan_start(&scanConf, true));
|
||||
break;
|
||||
}
|
||||
case ESP_BLUFI_EVENT_RECV_CUSTOM_DATA:
|
||||
BLUFI_INFO("Recv Custom Data %d\n", param->custom_data.data_len);
|
||||
esp_log_buffer_hex("Custom Data", param->custom_data.data, param->custom_data.data_len);
|
||||
break;
|
||||
case ESP_BLUFI_EVENT_RECV_USERNAME:
|
||||
/* Not handle currently */
|
||||
break;
|
||||
case ESP_BLUFI_EVENT_RECV_CA_CERT:
|
||||
/* Not handle currently */
|
||||
break;
|
||||
case ESP_BLUFI_EVENT_RECV_CLIENT_CERT:
|
||||
/* Not handle currently */
|
||||
break;
|
||||
case ESP_BLUFI_EVENT_RECV_SERVER_CERT:
|
||||
/* Not handle currently */
|
||||
break;
|
||||
case ESP_BLUFI_EVENT_RECV_CLIENT_PRIV_KEY:
|
||||
/* Not handle currently */
|
||||
break;;
|
||||
case ESP_BLUFI_EVENT_RECV_SERVER_PRIV_KEY:
|
||||
/* Not handle currently */
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void example_gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param)
|
||||
{
|
||||
switch (event) {
|
||||
case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT:
|
||||
esp_ble_gap_start_advertising(&example_adv_params);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
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 );
|
||||
|
||||
initialise_wifi();
|
||||
|
||||
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();
|
||||
ret = esp_bt_controller_init(&bt_cfg);
|
||||
if (ret) {
|
||||
BLUFI_ERROR("%s initialize bt controller failed: %s\n", __func__, esp_err_to_name(ret));
|
||||
}
|
||||
|
||||
ret = esp_bt_controller_enable(ESP_BT_MODE_BLE);
|
||||
if (ret) {
|
||||
BLUFI_ERROR("%s enable bt controller failed: %s\n", __func__, esp_err_to_name(ret));
|
||||
return;
|
||||
}
|
||||
|
||||
ret = esp_bluedroid_init();
|
||||
if (ret) {
|
||||
BLUFI_ERROR("%s init bluedroid failed: %s\n", __func__, esp_err_to_name(ret));
|
||||
return;
|
||||
}
|
||||
|
||||
ret = esp_bluedroid_enable();
|
||||
if (ret) {
|
||||
BLUFI_ERROR("%s init bluedroid failed: %s\n", __func__, esp_err_to_name(ret));
|
||||
return;
|
||||
}
|
||||
|
||||
BLUFI_INFO("BD ADDR: "ESP_BD_ADDR_STR"\n", ESP_BD_ADDR_HEX(esp_bt_dev_get_address()));
|
||||
|
||||
BLUFI_INFO("BLUFI VERSION %04x\n", esp_blufi_get_version());
|
||||
|
||||
ret = esp_ble_gap_register_callback(example_gap_event_handler);
|
||||
if(ret){
|
||||
BLUFI_ERROR("%s gap register failed, error code = %x\n", __func__, ret);
|
||||
return;
|
||||
}
|
||||
|
||||
ret = esp_blufi_register_callbacks(&example_callbacks);
|
||||
if(ret){
|
||||
BLUFI_ERROR("%s blufi register failed, error code = %x\n", __func__, ret);
|
||||
return;
|
||||
}
|
||||
|
||||
esp_blufi_profile_init();
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
/*
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/event_groups.h"
|
||||
#include "esp_system.h"
|
||||
#include "esp_wifi.h"
|
||||
#include "esp_event.h"
|
||||
#include "esp_log.h"
|
||||
#include "nvs_flash.h"
|
||||
#include "esp_bt.h"
|
||||
|
||||
#include "esp_blufi_api.h"
|
||||
#include "esp_bt_defs.h"
|
||||
#include "esp_gap_ble_api.h"
|
||||
#include "esp_bt_main.h"
|
||||
#include "blufi_example.h"
|
||||
|
||||
#include "mbedtls/aes.h"
|
||||
#include "mbedtls/dhm.h"
|
||||
#include "mbedtls/md5.h"
|
||||
#include "esp_crc.h"
|
||||
|
||||
/*
|
||||
The SEC_TYPE_xxx is for self-defined packet data type in the procedure of "BLUFI negotiate key"
|
||||
If user use other negotiation procedure to exchange(or generate) key, should redefine the type by yourself.
|
||||
*/
|
||||
#define SEC_TYPE_DH_PARAM_LEN 0x00
|
||||
#define SEC_TYPE_DH_PARAM_DATA 0x01
|
||||
#define SEC_TYPE_DH_P 0x02
|
||||
#define SEC_TYPE_DH_G 0x03
|
||||
#define SEC_TYPE_DH_PUBLIC 0x04
|
||||
|
||||
|
||||
struct blufi_security {
|
||||
#define DH_SELF_PUB_KEY_LEN 128
|
||||
#define DH_SELF_PUB_KEY_BIT_LEN (DH_SELF_PUB_KEY_LEN * 8)
|
||||
uint8_t self_public_key[DH_SELF_PUB_KEY_LEN];
|
||||
#define SHARE_KEY_LEN 128
|
||||
#define SHARE_KEY_BIT_LEN (SHARE_KEY_LEN * 8)
|
||||
uint8_t share_key[SHARE_KEY_LEN];
|
||||
size_t share_len;
|
||||
#define PSK_LEN 16
|
||||
uint8_t psk[PSK_LEN];
|
||||
uint8_t *dh_param;
|
||||
int dh_param_len;
|
||||
uint8_t iv[16];
|
||||
mbedtls_dhm_context dhm;
|
||||
mbedtls_aes_context aes;
|
||||
};
|
||||
static struct blufi_security *blufi_sec;
|
||||
|
||||
static int myrand( void *rng_state, unsigned char *output, size_t len )
|
||||
{
|
||||
esp_fill_random(output, len);
|
||||
return( 0 );
|
||||
}
|
||||
|
||||
extern void btc_blufi_report_error(esp_blufi_error_state_t state);
|
||||
|
||||
void blufi_dh_negotiate_data_handler(uint8_t *data, int len, uint8_t **output_data, int *output_len, bool *need_free)
|
||||
{
|
||||
int ret;
|
||||
uint8_t type = data[0];
|
||||
|
||||
if (blufi_sec == NULL) {
|
||||
BLUFI_ERROR("BLUFI Security is not initialized");
|
||||
btc_blufi_report_error(ESP_BLUFI_INIT_SECURITY_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case SEC_TYPE_DH_PARAM_LEN:
|
||||
blufi_sec->dh_param_len = ((data[1]<<8)|data[2]);
|
||||
if (blufi_sec->dh_param) {
|
||||
free(blufi_sec->dh_param);
|
||||
blufi_sec->dh_param = NULL;
|
||||
}
|
||||
blufi_sec->dh_param = (uint8_t *)malloc(blufi_sec->dh_param_len);
|
||||
if (blufi_sec->dh_param == NULL) {
|
||||
btc_blufi_report_error(ESP_BLUFI_DH_MALLOC_ERROR);
|
||||
BLUFI_ERROR("%s, malloc failed\n", __func__);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case SEC_TYPE_DH_PARAM_DATA:{
|
||||
if (blufi_sec->dh_param == NULL) {
|
||||
BLUFI_ERROR("%s, blufi_sec->dh_param == NULL\n", __func__);
|
||||
btc_blufi_report_error(ESP_BLUFI_DH_PARAM_ERROR);
|
||||
return;
|
||||
}
|
||||
uint8_t *param = blufi_sec->dh_param;
|
||||
memcpy(blufi_sec->dh_param, &data[1], blufi_sec->dh_param_len);
|
||||
ret = mbedtls_dhm_read_params(&blufi_sec->dhm, ¶m, ¶m[blufi_sec->dh_param_len]);
|
||||
if (ret) {
|
||||
BLUFI_ERROR("%s read param failed %d\n", __func__, ret);
|
||||
btc_blufi_report_error(ESP_BLUFI_READ_PARAM_ERROR);
|
||||
return;
|
||||
}
|
||||
free(blufi_sec->dh_param);
|
||||
blufi_sec->dh_param = NULL;
|
||||
ret = mbedtls_dhm_make_public(&blufi_sec->dhm, (int) mbedtls_mpi_size( &blufi_sec->dhm.P ), blufi_sec->self_public_key, blufi_sec->dhm.len, myrand, NULL);
|
||||
if (ret) {
|
||||
BLUFI_ERROR("%s make public failed %d\n", __func__, ret);
|
||||
btc_blufi_report_error(ESP_BLUFI_MAKE_PUBLIC_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
mbedtls_dhm_calc_secret( &blufi_sec->dhm,
|
||||
blufi_sec->share_key,
|
||||
SHARE_KEY_BIT_LEN,
|
||||
&blufi_sec->share_len,
|
||||
NULL, NULL);
|
||||
|
||||
mbedtls_md5(blufi_sec->share_key, blufi_sec->share_len, blufi_sec->psk);
|
||||
|
||||
mbedtls_aes_setkey_enc(&blufi_sec->aes, blufi_sec->psk, 128);
|
||||
|
||||
/* alloc output data */
|
||||
*output_data = &blufi_sec->self_public_key[0];
|
||||
*output_len = blufi_sec->dhm.len;
|
||||
*need_free = false;
|
||||
|
||||
}
|
||||
break;
|
||||
case SEC_TYPE_DH_P:
|
||||
break;
|
||||
case SEC_TYPE_DH_G:
|
||||
break;
|
||||
case SEC_TYPE_DH_PUBLIC:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int blufi_aes_encrypt(uint8_t iv8, uint8_t *crypt_data, int crypt_len)
|
||||
{
|
||||
int ret;
|
||||
size_t iv_offset = 0;
|
||||
uint8_t iv0[16];
|
||||
|
||||
memcpy(iv0, blufi_sec->iv, sizeof(blufi_sec->iv));
|
||||
iv0[0] = iv8; /* set iv8 as the iv0[0] */
|
||||
|
||||
ret = mbedtls_aes_crypt_cfb128(&blufi_sec->aes, MBEDTLS_AES_ENCRYPT, crypt_len, &iv_offset, iv0, crypt_data, crypt_data);
|
||||
if (ret) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return crypt_len;
|
||||
}
|
||||
|
||||
int blufi_aes_decrypt(uint8_t iv8, uint8_t *crypt_data, int crypt_len)
|
||||
{
|
||||
int ret;
|
||||
size_t iv_offset = 0;
|
||||
uint8_t iv0[16];
|
||||
|
||||
memcpy(iv0, blufi_sec->iv, sizeof(blufi_sec->iv));
|
||||
iv0[0] = iv8; /* set iv8 as the iv0[0] */
|
||||
|
||||
ret = mbedtls_aes_crypt_cfb128(&blufi_sec->aes, MBEDTLS_AES_DECRYPT, crypt_len, &iv_offset, iv0, crypt_data, crypt_data);
|
||||
if (ret) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return crypt_len;
|
||||
}
|
||||
|
||||
uint16_t blufi_crc_checksum(uint8_t iv8, uint8_t *data, int len)
|
||||
{
|
||||
/* This iv8 ignore, not used */
|
||||
return esp_crc16_be(0, data, len);
|
||||
}
|
||||
|
||||
esp_err_t blufi_security_init(void)
|
||||
{
|
||||
blufi_sec = (struct blufi_security *)malloc(sizeof(struct blufi_security));
|
||||
if (blufi_sec == NULL) {
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
memset(blufi_sec, 0x0, sizeof(struct blufi_security));
|
||||
|
||||
mbedtls_dhm_init(&blufi_sec->dhm);
|
||||
mbedtls_aes_init(&blufi_sec->aes);
|
||||
|
||||
memset(blufi_sec->iv, 0x0, 16);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void blufi_security_deinit(void)
|
||||
{
|
||||
if (blufi_sec == NULL) {
|
||||
return;
|
||||
}
|
||||
if (blufi_sec->dh_param){
|
||||
free(blufi_sec->dh_param);
|
||||
blufi_sec->dh_param = NULL;
|
||||
}
|
||||
mbedtls_dhm_free(&blufi_sec->dhm);
|
||||
mbedtls_aes_free(&blufi_sec->aes);
|
||||
|
||||
memset(blufi_sec, 0x0, sizeof(struct blufi_security));
|
||||
|
||||
free(blufi_sec);
|
||||
blufi_sec = NULL;
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
#
|
||||
# "main" pseudo-component makefile.
|
||||
#
|
||||
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
|
||||
@@ -0,0 +1,31 @@
|
||||
# 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_BTDM_CTRL_PINNED_TO_CORE_0=y
|
||||
CONFIG_BTDM_CTRL_PINNED_TO_CORE_1=n
|
||||
CONFIG_BTDM_CTRL_PINNED_TO_CORE=0
|
||||
CONFIG_BTDM_CTRL_HCI_MODE_VHCI=y
|
||||
CONFIG_BTDM_CTRL_HCI_MODE_UART_H4=n
|
||||
CONFIG_BT_BLUEDROID_ENABLED=y
|
||||
CONFIG_BT_BLUEDROID_PINNED_TO_CORE_0=y
|
||||
CONFIG_BT_BLUEDROID_PINNED_TO_CORE_1=n
|
||||
CONFIG_BT_BLUEDROID_PINNED_TO_CORE=0
|
||||
CONFIG_BT_BTC_TASK_STACK_SIZE=3072
|
||||
CONFIG_BT_BLUEDROID_MEM_DEBUG=n
|
||||
CONFIG_BT_CLASSIC_ENABLED=n
|
||||
CONFIG_BT_GATTS_ENABLE=y
|
||||
CONFIG_BT_GATTC_ENABLE=n
|
||||
CONFIG_BT_BLE_SMP_ENABLE=n
|
||||
CONFIG_BL_ENABLE_SRVCHG_REG=y
|
||||
CONFIG_BT_STACK_NO_LOG=n
|
||||
CONFIG_BT_ACL_CONNECTIONS=4
|
||||
CONFIG_BT_ALLOCATION_FROM_SPIRAM_FIRST=n
|
||||
CONFIG_BT_BLE_DYNAMIC_ENV_MEMORY=n
|
||||
CONFIG_BT_SMP_ENABLE=n
|
||||
@@ -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(gatt_client_demo)
|
||||
@@ -0,0 +1,10 @@
|
||||
#
|
||||
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
|
||||
# project subdirectory.
|
||||
#
|
||||
|
||||
PROJECT_NAME := gatt_client_demo
|
||||
|
||||
COMPONENT_ADD_INCLUDEDIRS := components/include
|
||||
|
||||
include $(IDF_PATH)/make/project.mk
|
||||
@@ -0,0 +1,13 @@
|
||||
| Supported Targets | ESP32 |
|
||||
| ----------------- | ----- |
|
||||
|
||||
ESP-IDF Gatt Client Demo
|
||||
========================
|
||||
|
||||
This is the demo for users to use ESP_APIs to create a GATT Client.
|
||||
|
||||
To test this demo, you can run the [gatt_server_demo](../gatt_server), which creates services and starts advertising. `Gatt_client_demo` will start scanning and connect to the `gatt_server_demo` automatically.
|
||||
|
||||
This demo will enable gatt server's notification function once the connection is established and then the devices start exchanging data.
|
||||
|
||||
Please check the [tutorial](tutorial/Gatt_Client_Example_Walkthrough.md) for more information about this example.
|
||||
@@ -0,0 +1,2 @@
|
||||
idf_component_register(SRCS "gattc_demo.c"
|
||||
INCLUDE_DIRS ".")
|
||||
@@ -0,0 +1,7 @@
|
||||
menu "Example Configuration"
|
||||
|
||||
config EXAMPLE_DUMP_ADV_DATA_AND_SCAN_RESP
|
||||
bool "Dump whole adv data and scan response data in example"
|
||||
default n
|
||||
|
||||
endmenu
|
||||
@@ -0,0 +1,4 @@
|
||||
#
|
||||
# "main" pseudo-component makefile.
|
||||
#
|
||||
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
|
||||
@@ -0,0 +1,500 @@
|
||||
/*
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
|
||||
|
||||
/****************************************************************************
|
||||
*
|
||||
* This demo showcases BLE GATT client. It can scan BLE devices and connect to one device.
|
||||
* Run the gatt_server demo, the client demo will automatically connect to the gatt_server demo.
|
||||
* Client demo will enable gatt_server's notify after connection. The two devices will then exchange
|
||||
* data.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include "nvs.h"
|
||||
#include "nvs_flash.h"
|
||||
|
||||
#include "esp_bt.h"
|
||||
#include "esp_gap_ble_api.h"
|
||||
#include "esp_gattc_api.h"
|
||||
#include "esp_gatt_defs.h"
|
||||
#include "esp_bt_main.h"
|
||||
#include "esp_gatt_common_api.h"
|
||||
#include "esp_log.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
|
||||
#define GATTC_TAG "GATTC_DEMO"
|
||||
#define REMOTE_SERVICE_UUID 0x00FF
|
||||
#define REMOTE_NOTIFY_CHAR_UUID 0xFF01
|
||||
#define PROFILE_NUM 1
|
||||
#define PROFILE_A_APP_ID 0
|
||||
#define INVALID_HANDLE 0
|
||||
|
||||
static const char remote_device_name[] = "ESP_GATTS_DEMO";
|
||||
static bool connect = false;
|
||||
static bool get_server = false;
|
||||
static esp_gattc_char_elem_t *char_elem_result = NULL;
|
||||
static esp_gattc_descr_elem_t *descr_elem_result = NULL;
|
||||
|
||||
/* Declare static functions */
|
||||
static void esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param);
|
||||
static void esp_gattc_cb(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param);
|
||||
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 esp_bt_uuid_t remote_filter_service_uuid = {
|
||||
.len = ESP_UUID_LEN_16,
|
||||
.uuid = {.uuid16 = REMOTE_SERVICE_UUID,},
|
||||
};
|
||||
|
||||
static esp_bt_uuid_t remote_filter_char_uuid = {
|
||||
.len = ESP_UUID_LEN_16,
|
||||
.uuid = {.uuid16 = REMOTE_NOTIFY_CHAR_UUID,},
|
||||
};
|
||||
|
||||
static esp_bt_uuid_t notify_descr_uuid = {
|
||||
.len = ESP_UUID_LEN_16,
|
||||
.uuid = {.uuid16 = ESP_GATT_UUID_CHAR_CLIENT_CONFIG,},
|
||||
};
|
||||
|
||||
static esp_ble_scan_params_t ble_scan_params = {
|
||||
.scan_type = BLE_SCAN_TYPE_ACTIVE,
|
||||
.own_addr_type = BLE_ADDR_TYPE_PUBLIC,
|
||||
.scan_filter_policy = BLE_SCAN_FILTER_ALLOW_ALL,
|
||||
.scan_interval = 0x50,
|
||||
.scan_window = 0x30,
|
||||
.scan_duplicate = BLE_SCAN_DUPLICATE_DISABLE
|
||||
};
|
||||
|
||||
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 char_handle;
|
||||
esp_bd_addr_t remote_bda;
|
||||
};
|
||||
|
||||
/* One gatt-based profile one app_id and one gattc_if, this array will store the gattc_if returned by ESP_GATTS_REG_EVT */
|
||||
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, /* Not get the gatt_if, so initial is ESP_GATT_IF_NONE */
|
||||
},
|
||||
};
|
||||
|
||||
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)
|
||||
{
|
||||
esp_ble_gattc_cb_param_t *p_data = (esp_ble_gattc_cb_param_t *)param;
|
||||
|
||||
switch (event) {
|
||||
case ESP_GATTC_REG_EVT:
|
||||
ESP_LOGI(GATTC_TAG, "REG_EVT");
|
||||
esp_err_t scan_ret = esp_ble_gap_set_scan_params(&ble_scan_params);
|
||||
if (scan_ret){
|
||||
ESP_LOGE(GATTC_TAG, "set scan params error, error code = %x", scan_ret);
|
||||
}
|
||||
break;
|
||||
case ESP_GATTC_CONNECT_EVT:{
|
||||
ESP_LOGI(GATTC_TAG, "ESP_GATTC_CONNECT_EVT conn_id %d, if %d", p_data->connect.conn_id, gattc_if);
|
||||
gl_profile_tab[PROFILE_A_APP_ID].conn_id = p_data->connect.conn_id;
|
||||
memcpy(gl_profile_tab[PROFILE_A_APP_ID].remote_bda, p_data->connect.remote_bda, sizeof(esp_bd_addr_t));
|
||||
ESP_LOGI(GATTC_TAG, "REMOTE BDA:");
|
||||
esp_log_buffer_hex(GATTC_TAG, gl_profile_tab[PROFILE_A_APP_ID].remote_bda, sizeof(esp_bd_addr_t));
|
||||
esp_err_t mtu_ret = esp_ble_gattc_send_mtu_req (gattc_if, p_data->connect.conn_id);
|
||||
if (mtu_ret){
|
||||
ESP_LOGE(GATTC_TAG, "config MTU error, error code = %x", mtu_ret);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_OPEN_EVT:
|
||||
if (param->open.status != ESP_GATT_OK){
|
||||
ESP_LOGE(GATTC_TAG, "open failed, status %d", p_data->open.status);
|
||||
break;
|
||||
}
|
||||
ESP_LOGI(GATTC_TAG, "open success");
|
||||
break;
|
||||
case ESP_GATTC_DIS_SRVC_CMPL_EVT:
|
||||
if (param->dis_srvc_cmpl.status != ESP_GATT_OK){
|
||||
ESP_LOGE(GATTC_TAG, "discover service failed, status %d", param->dis_srvc_cmpl.status);
|
||||
break;
|
||||
}
|
||||
ESP_LOGI(GATTC_TAG, "discover service complete conn_id %d", param->dis_srvc_cmpl.conn_id);
|
||||
esp_ble_gattc_search_service(gattc_if, param->cfg_mtu.conn_id, &remote_filter_service_uuid);
|
||||
break;
|
||||
case ESP_GATTC_CFG_MTU_EVT:
|
||||
if (param->cfg_mtu.status != ESP_GATT_OK){
|
||||
ESP_LOGE(GATTC_TAG,"config mtu failed, error status = %x", param->cfg_mtu.status);
|
||||
}
|
||||
ESP_LOGI(GATTC_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);
|
||||
break;
|
||||
case ESP_GATTC_SEARCH_RES_EVT: {
|
||||
ESP_LOGI(GATTC_TAG, "SEARCH RES: conn_id = %x is primary service %d", p_data->search_res.conn_id, p_data->search_res.is_primary);
|
||||
ESP_LOGI(GATTC_TAG, "start handle %d end handle %d current handle value %d", p_data->search_res.start_handle, p_data->search_res.end_handle, p_data->search_res.srvc_id.inst_id);
|
||||
if (p_data->search_res.srvc_id.uuid.len == ESP_UUID_LEN_16 && p_data->search_res.srvc_id.uuid.uuid.uuid16 == REMOTE_SERVICE_UUID) {
|
||||
ESP_LOGI(GATTC_TAG, "service found");
|
||||
get_server = true;
|
||||
gl_profile_tab[PROFILE_A_APP_ID].service_start_handle = p_data->search_res.start_handle;
|
||||
gl_profile_tab[PROFILE_A_APP_ID].service_end_handle = p_data->search_res.end_handle;
|
||||
ESP_LOGI(GATTC_TAG, "UUID16: %x", p_data->search_res.srvc_id.uuid.uuid.uuid16);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_SEARCH_CMPL_EVT:
|
||||
if (p_data->search_cmpl.status != ESP_GATT_OK){
|
||||
ESP_LOGE(GATTC_TAG, "search service failed, error status = %x", p_data->search_cmpl.status);
|
||||
break;
|
||||
}
|
||||
if(p_data->search_cmpl.searched_service_source == ESP_GATT_SERVICE_FROM_REMOTE_DEVICE) {
|
||||
ESP_LOGI(GATTC_TAG, "Get service information from remote device");
|
||||
} else if (p_data->search_cmpl.searched_service_source == ESP_GATT_SERVICE_FROM_NVS_FLASH) {
|
||||
ESP_LOGI(GATTC_TAG, "Get service information from flash");
|
||||
} else {
|
||||
ESP_LOGI(GATTC_TAG, "unknown service source");
|
||||
}
|
||||
ESP_LOGI(GATTC_TAG, "ESP_GATTC_SEARCH_CMPL_EVT");
|
||||
if (get_server){
|
||||
uint16_t count = 0;
|
||||
esp_gatt_status_t status = esp_ble_gattc_get_attr_count( gattc_if,
|
||||
p_data->search_cmpl.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 (status != ESP_GATT_OK){
|
||||
ESP_LOGE(GATTC_TAG, "esp_ble_gattc_get_attr_count error");
|
||||
}
|
||||
|
||||
if (count > 0){
|
||||
char_elem_result = (esp_gattc_char_elem_t *)malloc(sizeof(esp_gattc_char_elem_t) * count);
|
||||
if (!char_elem_result){
|
||||
ESP_LOGE(GATTC_TAG, "gattc no mem");
|
||||
}else{
|
||||
status = esp_ble_gattc_get_char_by_uuid( gattc_if,
|
||||
p_data->search_cmpl.conn_id,
|
||||
gl_profile_tab[PROFILE_A_APP_ID].service_start_handle,
|
||||
gl_profile_tab[PROFILE_A_APP_ID].service_end_handle,
|
||||
remote_filter_char_uuid,
|
||||
char_elem_result,
|
||||
&count);
|
||||
if (status != ESP_GATT_OK){
|
||||
ESP_LOGE(GATTC_TAG, "esp_ble_gattc_get_char_by_uuid error");
|
||||
}
|
||||
|
||||
/* Every service have only one char in our 'ESP_GATTS_DEMO' demo, so we used first 'char_elem_result' */
|
||||
if (count > 0 && (char_elem_result[0].properties & ESP_GATT_CHAR_PROP_BIT_NOTIFY)){
|
||||
gl_profile_tab[PROFILE_A_APP_ID].char_handle = char_elem_result[0].char_handle;
|
||||
esp_ble_gattc_register_for_notify (gattc_if, gl_profile_tab[PROFILE_A_APP_ID].remote_bda, char_elem_result[0].char_handle);
|
||||
}
|
||||
}
|
||||
/* free char_elem_result */
|
||||
free(char_elem_result);
|
||||
}else{
|
||||
ESP_LOGE(GATTC_TAG, "no char found");
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
|
||||
ESP_LOGI(GATTC_TAG, "ESP_GATTC_REG_FOR_NOTIFY_EVT");
|
||||
if (p_data->reg_for_notify.status != ESP_GATT_OK){
|
||||
ESP_LOGE(GATTC_TAG, "REG FOR NOTIFY failed: error status = %d", p_data->reg_for_notify.status);
|
||||
}else{
|
||||
uint16_t count = 0;
|
||||
uint16_t notify_en = 1;
|
||||
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_DESCRIPTOR,
|
||||
gl_profile_tab[PROFILE_A_APP_ID].service_start_handle,
|
||||
gl_profile_tab[PROFILE_A_APP_ID].service_end_handle,
|
||||
gl_profile_tab[PROFILE_A_APP_ID].char_handle,
|
||||
&count);
|
||||
if (ret_status != ESP_GATT_OK){
|
||||
ESP_LOGE(GATTC_TAG, "esp_ble_gattc_get_attr_count error");
|
||||
}
|
||||
if (count > 0){
|
||||
descr_elem_result = malloc(sizeof(esp_gattc_descr_elem_t) * count);
|
||||
if (!descr_elem_result){
|
||||
ESP_LOGE(GATTC_TAG, "malloc error, gattc no mem");
|
||||
}else{
|
||||
ret_status = esp_ble_gattc_get_descr_by_char_handle( gattc_if,
|
||||
gl_profile_tab[PROFILE_A_APP_ID].conn_id,
|
||||
p_data->reg_for_notify.handle,
|
||||
notify_descr_uuid,
|
||||
descr_elem_result,
|
||||
&count);
|
||||
if (ret_status != ESP_GATT_OK){
|
||||
ESP_LOGE(GATTC_TAG, "esp_ble_gattc_get_descr_by_char_handle error");
|
||||
}
|
||||
/* Every char has only one descriptor in our 'ESP_GATTS_DEMO' demo, so we used first 'descr_elem_result' */
|
||||
if (count > 0 && descr_elem_result[0].uuid.len == ESP_UUID_LEN_16 && descr_elem_result[0].uuid.uuid.uuid16 == ESP_GATT_UUID_CHAR_CLIENT_CONFIG){
|
||||
ret_status = esp_ble_gattc_write_char_descr( gattc_if,
|
||||
gl_profile_tab[PROFILE_A_APP_ID].conn_id,
|
||||
descr_elem_result[0].handle,
|
||||
sizeof(notify_en),
|
||||
(uint8_t *)¬ify_en,
|
||||
ESP_GATT_WRITE_TYPE_RSP,
|
||||
ESP_GATT_AUTH_REQ_NONE);
|
||||
}
|
||||
|
||||
if (ret_status != ESP_GATT_OK){
|
||||
ESP_LOGE(GATTC_TAG, "esp_ble_gattc_write_char_descr error");
|
||||
}
|
||||
|
||||
/* free descr_elem_result */
|
||||
free(descr_elem_result);
|
||||
}
|
||||
}
|
||||
else{
|
||||
ESP_LOGE(GATTC_TAG, "decsr not found");
|
||||
}
|
||||
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_NOTIFY_EVT:
|
||||
if (p_data->notify.is_notify){
|
||||
ESP_LOGI(GATTC_TAG, "ESP_GATTC_NOTIFY_EVT, receive notify value:");
|
||||
}else{
|
||||
ESP_LOGI(GATTC_TAG, "ESP_GATTC_NOTIFY_EVT, receive indicate value:");
|
||||
}
|
||||
esp_log_buffer_hex(GATTC_TAG, p_data->notify.value, p_data->notify.value_len);
|
||||
break;
|
||||
case ESP_GATTC_WRITE_DESCR_EVT:
|
||||
if (p_data->write.status != ESP_GATT_OK){
|
||||
ESP_LOGE(GATTC_TAG, "write descr failed, error status = %x", p_data->write.status);
|
||||
break;
|
||||
}
|
||||
ESP_LOGI(GATTC_TAG, "write descr success ");
|
||||
uint8_t write_char_data[35];
|
||||
for (int i = 0; i < sizeof(write_char_data); ++i)
|
||||
{
|
||||
write_char_data[i] = i % 256;
|
||||
}
|
||||
esp_ble_gattc_write_char( gattc_if,
|
||||
gl_profile_tab[PROFILE_A_APP_ID].conn_id,
|
||||
gl_profile_tab[PROFILE_A_APP_ID].char_handle,
|
||||
sizeof(write_char_data),
|
||||
write_char_data,
|
||||
ESP_GATT_WRITE_TYPE_RSP,
|
||||
ESP_GATT_AUTH_REQ_NONE);
|
||||
break;
|
||||
case ESP_GATTC_SRVC_CHG_EVT: {
|
||||
esp_bd_addr_t bda;
|
||||
memcpy(bda, p_data->srvc_chg.remote_bda, sizeof(esp_bd_addr_t));
|
||||
ESP_LOGI(GATTC_TAG, "ESP_GATTC_SRVC_CHG_EVT, bd_addr:");
|
||||
esp_log_buffer_hex(GATTC_TAG, bda, sizeof(esp_bd_addr_t));
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_WRITE_CHAR_EVT:
|
||||
if (p_data->write.status != ESP_GATT_OK){
|
||||
ESP_LOGE(GATTC_TAG, "write char failed, error status = %x", p_data->write.status);
|
||||
break;
|
||||
}
|
||||
ESP_LOGI(GATTC_TAG, "write char success ");
|
||||
break;
|
||||
case ESP_GATTC_DISCONNECT_EVT:
|
||||
connect = false;
|
||||
get_server = false;
|
||||
ESP_LOGI(GATTC_TAG, "ESP_GATTC_DISCONNECT_EVT, reason = %d", p_data->disconnect.reason);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param)
|
||||
{
|
||||
uint8_t *adv_name = NULL;
|
||||
uint8_t adv_name_len = 0;
|
||||
switch (event) {
|
||||
case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT: {
|
||||
//the unit of the duration is second
|
||||
uint32_t duration = 30;
|
||||
esp_ble_gap_start_scanning(duration);
|
||||
break;
|
||||
}
|
||||
case ESP_GAP_BLE_SCAN_START_COMPLETE_EVT:
|
||||
//scan start complete event to indicate scan start successfully or failed
|
||||
if (param->scan_start_cmpl.status != ESP_BT_STATUS_SUCCESS) {
|
||||
ESP_LOGE(GATTC_TAG, "scan start failed, error status = %x", param->scan_start_cmpl.status);
|
||||
break;
|
||||
}
|
||||
ESP_LOGI(GATTC_TAG, "scan start success");
|
||||
|
||||
break;
|
||||
case ESP_GAP_BLE_SCAN_RESULT_EVT: {
|
||||
esp_ble_gap_cb_param_t *scan_result = (esp_ble_gap_cb_param_t *)param;
|
||||
switch (scan_result->scan_rst.search_evt) {
|
||||
case ESP_GAP_SEARCH_INQ_RES_EVT:
|
||||
esp_log_buffer_hex(GATTC_TAG, scan_result->scan_rst.bda, 6);
|
||||
ESP_LOGI(GATTC_TAG, "searched Adv Data Len %d, Scan Response Len %d", scan_result->scan_rst.adv_data_len, scan_result->scan_rst.scan_rsp_len);
|
||||
adv_name = esp_ble_resolve_adv_data(scan_result->scan_rst.ble_adv,
|
||||
ESP_BLE_AD_TYPE_NAME_CMPL, &adv_name_len);
|
||||
ESP_LOGI(GATTC_TAG, "searched Device Name Len %d", adv_name_len);
|
||||
esp_log_buffer_char(GATTC_TAG, adv_name, adv_name_len);
|
||||
|
||||
#if CONFIG_EXAMPLE_DUMP_ADV_DATA_AND_SCAN_RESP
|
||||
if (scan_result->scan_rst.adv_data_len > 0) {
|
||||
ESP_LOGI(GATTC_TAG, "adv data:");
|
||||
esp_log_buffer_hex(GATTC_TAG, &scan_result->scan_rst.ble_adv[0], scan_result->scan_rst.adv_data_len);
|
||||
}
|
||||
if (scan_result->scan_rst.scan_rsp_len > 0) {
|
||||
ESP_LOGI(GATTC_TAG, "scan resp:");
|
||||
esp_log_buffer_hex(GATTC_TAG, &scan_result->scan_rst.ble_adv[scan_result->scan_rst.adv_data_len], scan_result->scan_rst.scan_rsp_len);
|
||||
}
|
||||
#endif
|
||||
ESP_LOGI(GATTC_TAG, "\n");
|
||||
|
||||
if (adv_name != NULL) {
|
||||
if (strlen(remote_device_name) == adv_name_len && strncmp((char *)adv_name, remote_device_name, adv_name_len) == 0) {
|
||||
ESP_LOGI(GATTC_TAG, "searched device %s\n", remote_device_name);
|
||||
if (connect == false) {
|
||||
connect = true;
|
||||
ESP_LOGI(GATTC_TAG, "connect to the remote device.");
|
||||
esp_ble_gap_stop_scanning();
|
||||
esp_ble_gattc_open(gl_profile_tab[PROFILE_A_APP_ID].gattc_if, scan_result->scan_rst.bda, scan_result->scan_rst.ble_addr_type, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ESP_GAP_SEARCH_INQ_CMPL_EVT:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT:
|
||||
if (param->scan_stop_cmpl.status != ESP_BT_STATUS_SUCCESS){
|
||||
ESP_LOGE(GATTC_TAG, "scan stop failed, error status = %x", param->scan_stop_cmpl.status);
|
||||
break;
|
||||
}
|
||||
ESP_LOGI(GATTC_TAG, "stop scan successfully");
|
||||
break;
|
||||
|
||||
case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT:
|
||||
if (param->adv_stop_cmpl.status != ESP_BT_STATUS_SUCCESS){
|
||||
ESP_LOGE(GATTC_TAG, "adv stop failed, error status = %x", param->adv_stop_cmpl.status);
|
||||
break;
|
||||
}
|
||||
ESP_LOGI(GATTC_TAG, "stop adv successfully");
|
||||
break;
|
||||
case ESP_GAP_BLE_UPDATE_CONN_PARAMS_EVT:
|
||||
ESP_LOGI(GATTC_TAG, "update connection params status = %d, min_int = %d, max_int = %d,conn_int = %d,latency = %d, timeout = %d",
|
||||
param->update_conn_params.status,
|
||||
param->update_conn_params.min_int,
|
||||
param->update_conn_params.max_int,
|
||||
param->update_conn_params.conn_int,
|
||||
param->update_conn_params.latency,
|
||||
param->update_conn_params.timeout);
|
||||
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 is register event, store the gattc_if for each profile */
|
||||
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(GATTC_TAG, "reg app failed, app_id %04x, status %d",
|
||||
param->reg.app_id,
|
||||
param->reg.status);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* If the gattc_if equal to profile A, call profile A cb handler,
|
||||
* so here call each profile's callback */
|
||||
do {
|
||||
int idx;
|
||||
for (idx = 0; idx < PROFILE_NUM; idx++) {
|
||||
if (gattc_if == ESP_GATT_IF_NONE || /* ESP_GATT_IF_NONE, not specify a certain gatt_if, need to call every profile cb function */
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
} while (0);
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
// Initialize NVS.
|
||||
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();
|
||||
ret = esp_bt_controller_init(&bt_cfg);
|
||||
if (ret) {
|
||||
ESP_LOGE(GATTC_TAG, "%s initialize controller failed: %s\n", __func__, esp_err_to_name(ret));
|
||||
return;
|
||||
}
|
||||
|
||||
ret = esp_bt_controller_enable(ESP_BT_MODE_BLE);
|
||||
if (ret) {
|
||||
ESP_LOGE(GATTC_TAG, "%s enable controller failed: %s\n", __func__, esp_err_to_name(ret));
|
||||
return;
|
||||
}
|
||||
|
||||
ret = esp_bluedroid_init();
|
||||
if (ret) {
|
||||
ESP_LOGE(GATTC_TAG, "%s init bluetooth failed: %s\n", __func__, esp_err_to_name(ret));
|
||||
return;
|
||||
}
|
||||
|
||||
ret = esp_bluedroid_enable();
|
||||
if (ret) {
|
||||
ESP_LOGE(GATTC_TAG, "%s enable bluetooth failed: %s\n", __func__, esp_err_to_name(ret));
|
||||
return;
|
||||
}
|
||||
|
||||
//register the callback function to the gap module
|
||||
ret = esp_ble_gap_register_callback(esp_gap_cb);
|
||||
if (ret){
|
||||
ESP_LOGE(GATTC_TAG, "%s gap register failed, error code = %x\n", __func__, ret);
|
||||
return;
|
||||
}
|
||||
|
||||
//register the callback function to the gattc module
|
||||
ret = esp_ble_gattc_register_callback(esp_gattc_cb);
|
||||
if(ret){
|
||||
ESP_LOGE(GATTC_TAG, "%s gattc register failed, error code = %x\n", __func__, ret);
|
||||
return;
|
||||
}
|
||||
|
||||
ret = esp_ble_gattc_app_register(PROFILE_A_APP_ID);
|
||||
if (ret){
|
||||
ESP_LOGE(GATTC_TAG, "%s gattc app register failed, error code = %x\n", __func__, ret);
|
||||
}
|
||||
esp_err_t local_mtu_ret = esp_ble_gatt_set_local_mtu(500);
|
||||
if (local_mtu_ret){
|
||||
ESP_LOGE(GATTC_TAG, "set local MTU failed, error code = %x", local_mtu_ret);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
# Override some defaults so BT stack is enabled
|
||||
# by default in this example
|
||||
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
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user