一、 简介

1.1 硬件介绍

ESP32-S3 SoC 芯片支持以下功能:

  • 2.4 GHz Wi-Fi
  • 低功耗蓝牙
  • 高性能 Xtensa® 32 位 LX7 双核处理器
  • 运行 RISC-V 或 FSM 内核的超低功耗协处理器
  • 多种外设
  • 内置安全硬件
  • USB OTG 接口
  • USB 串口/JTAG 控制

1.2 官方资料

ESP-IDF编程指南
蓝牙API
GATT服务器API

1.3 开发环境

软件:IDF 5.1.2
硬件:ESP32-S3-LCD-EV-Board-MB 开发

1.4 蓝牙介绍

1.4.1 蓝牙协议栈架构

低功耗蓝牙协议栈框架结构如下所示:
ble协议栈架构
如图所示,ATT和GATT是蓝牙协议栈重要的2层,也是蓝牙应用开发者打交道最多的两层,用户开发应用程序或者说service/profile的时候,调用的都是GATT API,而GATT又调用了ATT API。

1.4.2 BLE client/server架构

BLE采用client/server(C/S)架构进行数据交互,client/server架构是一种常见的架构,比如我们经常用到的浏览器和服务器也是一种C/S架构,这其中浏览器是客户端client,服务器是服务端server。BLE与此类似,一般而言设备提供服务,因此设备是server,手机使用设备提供的服务,因此手机是client。比如蓝牙手表,它可以提供 “体温”、“心率” 数据服务,因此是一个server,而手机则可以请求“体温”、“心率”数据以显示在手机上,因此手机是一个client。
ble架构
客户端要访问某一个数据,就发送一个request请求,服务端再把该数据返回给客户端,即C/S架构。

1.4.3 ATT

ATT代表属性协议(Attribute Protocol)。它是BLE规范中定义的一个简单的客户端/服务器协议,用于设备间的数据交换和通信。ATT协议在BLE设备中扮演着核心角色,它定义了如何以小型数据包的形式在低功耗设备之间传输数据。

主要特点和功能

  • 属性:在ATT协议中,所有的数据都被组织成属性(Attributes)。每个属性都有一个唯一的标识符(UUID)、一个值以及一组权限(如可读、可写)。
  • 客户端与服务器模型:ATT协议采用客户端/服务器架构。服务器设备存储属性(例如,传感器数据或设备配置),客户端设备读取或写入这些属性。
  • GATT协议:在ATT的基础上,BLE引入了通用属性配置文件(Generic Attribute Profile, GATT),它使用ATT协议来定义设备服务和特性的结构。几乎所有的BLE应用程序都是基于GATT来实现的。
  • 连接优化:ATT设计用于优化和减少需要在客户端和服务器之间传输的数据量,非常适合于低功耗设备和低带宽的通信环境。

ATT协议的应用场景

  • 数据交换:通过ATT协议,BLE设备可以交换各种类型的数据,如健康监测器上的心率数据、环境传感器上的温湿度数据等。
  • 设备控制:客户端设备可以通过写入服务器设备的特定属性来控制设备行为,如打开或关闭智能灯泡。
  • 配置和状态信息:客户端设备可以读取服务器设备的配置和状态信息,如获取智能手表的电池电量。

Attributes可以用下图来表示:
在这里插入图片描述

  • Attribute handle,Attribute句柄,16-bit长度。Client要访问Server的Attribute,都是通过这个句柄来访问的,也就是说ATT PDU一般都包含handle的值。用户在软件代码添加characteristic的时候,系统会自动按顺序地为相关attribute生成句柄。
  • Attribute type,Attribute类型,2字节或者16字节长。在BLE中我们使用UUID来定义数据的类型,UUID是128 bit的,所以我们有足够的UUID来表达万事万物。
    Attribute type一般是由service和characteristic规格来定义,站在蓝牙协议栈角度来看,ATT层定义了一个通信的基本框架,数据的基本结构,以及通信的指令,而GATT层就是定义service和characteristic,GATT层用来赋予每个数据一个具体的内涵,让数据变得有结构和意义。换句话说,没有GATT层,低功耗蓝牙也可以通信起来,但会产生兼容性问题以及通信的低效率。
  • Attribute value,就是数据真正的值,0到512字节长。
  • Attribute permissions,Attribute的权限属性,权限属性不会直接在空口包中体现,而是隐含在ATT命令的操作结果中。假设一个attribute read属性设为open(即读操作不需要任何权限),那么client去读这个attribute时server将直接返回attribute的值;如果这个attribute read属性设为authentication(即需要配对才能访问),如果client没有与server配对而直接去访问这个attribute,那么server会返回一个错误码:告诉client你的权限不够,此时client会对server发起配对请求,以满足这个attribute的读属性要求,从而在第二次读操作时server将把相应的数据返回给client。目前主要有如下四种权限属性:
  • Open,直接可以读或者写
  • No Access,禁止读或者写
  • Authentication,需要配对才能读或者写,由于配对有多种类型,因此authentication又衍生多种子类型,比如带不带MITM,有没有LESC
  • Authorization,跟open一样,不过server返回attribute的值之前需要应用先授权,也就是说应用可以在回调函数里面去修改读或者写的原始值。
  • Signed,签名后才能读或者写,这个用得比较少。

一个应用所有的attribute组成一个database,也称为attribute table,一个attribute table示例如下所示:
在这里插入图片描述

1.4.4 GATT

GATT代表通用属性配置文件(Generic Attribute Profile)。它是建立在属性协议(ATT)之上的一个高级协议,用于通过BLE连接定义设备如何交换数据。GATT为设备之间的数据交互提供了框架,使得开发者能够定义如何发现服务、如何读写特性等。

主要组成部分

  • 服务(Services):GATT服务是一组相关特性的集合,用于封装设备提供的功能。每个服务由一个唯一的UUID来标识,可以包含多个特性(Characteristics)和/或引用其他服务。
  • 特性(Characteristics):特性是服务中的数据点,包含一个值和0到多个描述符(Descriptors)。特性用于定义服务的具体功能,比如一个“心率测量”服务可以包含一个“心率值”特性。
  • 描述符(Descriptors):描述符用于描述特性的属性和配置信息,例如特性的可读性、可写性等。

GATT的工作原理

  • 客户端/服务器模型:GATT操作基于客户端/服务器模型。服务器设备持有数据并提供GATT服务和特性,客户端设备通过GATT操作来请求数据或对数据进行更改。
  • 发现服务和特性:客户端首先需要发现服务器提供的服务和特性。这通过GATT发现协议来实现,客户端可以请求服务器列出其所有服务和服务中的特性。
  • 读写操作:一旦发现了服务和特性,客户端可以使用GATT读写协议来读取或写入特性的值。这些操作包括读取特性值、写入特性值、通知(Notifications)和指示(Indications),后两者允许服务器在特性值改变时主动通知客户端。

GATT的应用场景

  • 健康和健身:心率监测器、健身追踪器等设备使用GATT服务来传输健康相关数据。
  • 智能家居:智能灯泡、门锁和温控器等设备利用GATT来实现控制和状态监测。
  • 位置和导航:位置追踪设备和导航设备通过GATT服务提供位置数据。

GATT通过定义标准化的服务和特性,简化了BLE设备的开发和互操作性,使得开发者可以专注于应用逻辑,而无需担心底层的数据传输细节。

1.5 iBeacon介绍

iBeacon是苹果公司在2013年推出的一种基于蓝牙低功耗(BLE, Bluetooth Low Energy)技术的室内定位系统技术标准。它允许移动应用(在iOS和Android设备上)侦听信号,并在接近iBeacon设备时做出响应。iBeacon技术主要用于室内定位和导航、上下文感知广告推送、客户行为分析等场景。

工作原理
iBeacon设备(被称为Beacon)是一种低功耗的硬件传输器,可以在一定范围内广播其唯一的标识符给附近的智能设备。当一个兼容的应用或操作系统检测到这个信号时,它可以根据信号的强度判断出与Beacon的大致距离,并根据这个信息执行特定的动作,如推送通知、记录日志、触发事件等。

iBeacon数据包
iBeacon广播的数据包主要包含以下几个关键信息:

  • UUID(Universally Unique Identifier):应用特定的唯一标识符,用于区分不同的iBeacon部署。
  • Major和Minor:这两个值用于识别一组iBeacons中的特定的Beacon,以进一步细分UUID下的区域或使用场景。
  • 信号强度(Measured Power):这个值用于确定设备与Beacon的距离。通过比较广播信号的强度(RSSI)和Measured Power,应用可以估算出用户设备与Beacon的距离。

应用场景

  • 零售行业:商店可以使用iBeacon技术来向顾客的手机发送促销信息、优惠券和其他相关信息,或者分析顾客在商店内的行为模式。
  • 展会和会议:通过部署iBeacon,组织者可以提供基于位置的信息和服务,如导航、日程安排提醒等。
  • 博物馆和展览:iBeacon可以提供室内导航帮助游客找到展览物品,并提供关于展览品的详细信息。
  • 机场:iBeacon技术可用于提供航班信息更新、导航至登机口、行李领取等服务。

优势

  • 低功耗:iBeacon设备通常由电池供电,可以持续运行数月甚至数年。
  • 易于部署:iBeacon设备小巧便携,安装简单,维护成本低。

iBeacon技术为基于位置的服务提供了一个简单而强大的工具,但它的实际应用效果往往取决于用户设备的兼容性、应用设计的巧妙以及环境因素的影响。

二、iBeacon广播

在本次实验中实现如下功能:

  1. 开启iBeacon广播
  2. 可通过串口改变广播蓝牙广播间隔

2.1 添加代码

2.1.1 main.c文件修改

此时将main.c文件修改为以下内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
/*
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/


#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"
#include "esp_ibeacon_api.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_iBeacon" //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[30] = {
// /* Flags */
// 0x02,0x01,0x06,
// /* Complete Local Name in advertising */

// /* Complete List of 16-bit Service Class UUIDs */
// 0x1A,0xFF, 0x4c, 0x00,0x02,0x15,
// 0xFD, 0xA5, 0x06, 0x93, 0xA4, 0xE2, 0x4F, 0xB1, 0xAF, 0xCF, 0xC6, 0xEb, 0x07, 0x64, 0x78,0x25,0x27,0xb7,0xf2,0x06,0xC5,
// //0x08,0x09, 'i', 'B', 'e', 'a', 'c', 'o', 'n',

// };
static const uint8_t scan_adv_data[10] = {
0x08,0x09, 'i', 'B', 'e', 'a', 'c', 'o', 'n',
};

extern esp_ble_ibeacon_vendor_t vendor_config;

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 QueueHandle_t cmd_cmd_queue = NULL;

#ifdef SUPPORT_HEARTBEAT
static QueueHandle_t 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];

int adv_int;

uint16_t get_adv(int adv_int)
{
return (adv_int/0.625);
}

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, (TickType_t)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_DEFAULT,
};

//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)) {
adv_int = atoi((char *)cmd_id);
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",4096, 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:
adv_int = 200;
spp_adv_params.adv_int_max = get_adv(200);
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_ble_ibeacon_t ibeacon_adv_data;

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_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(GATTS_TABLE_TAG, "Config iBeacon data failed: %s\n", esp_err_to_name(status));
}
// esp_ble_gap_config_adv_data_raw((uint8_t *)spp_adv_data, sizeof(spp_adv_data));
esp_ble_gap_config_scan_rsp_data_raw((uint8_t *)scan_adv_data, sizeof(scan_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
spp_adv_params.adv_int_max = get_adv(adv_int);
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;
}

2.1.2 iBeacon代码添加

在main文件夹下新建一个esp_ibeacon_api.c文件,用于存放ibeacon代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
/*
* SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/



/****************************************************************************
*
* 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;
}

在main文件夹下新建一个esp_ibeacon_api.h文件,用于存放ibeacon头文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
/*
* SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/


/****************************************************************************
*
* 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);

2.1.2 透传服务代码添加

在main文件夹下新建一个ble_spp_server_demo.h文件,用于存放透传服务头文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
/*
* SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/


#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/*
* 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,
};

2.1.3 CMakeList.txt文件修改

将添加的文件及文件夹路径添加进去

1
2
3
4
5
idf_component_register(SRCS 
"main.c"
"esp_ibeacon_api.c"
INCLUDE_DIRS
".")

2.2 实验现象

使用nrf connect手机app查看蓝牙广播
查看打印:
在这里插入图片描述
查看广播:
在这里插入图片描述

三、代码讲解

3.1 main函数部分

nvs_flash_init()函数用于初始化非易失性存储(NVS)的FLASH部分。
NVS(Non-Volatile Storage)是一种用于在非易失性存储器中保存键值对数据的服务。NVS库为在ESP32的FLASH存储中持久化保存应用程序设置和其他数据提供了一个简单且高效的方式。使用NVS,你可以在设备断电或重启后保留数据,这对于需要存储配置参数、用户偏好或其他必须跨会话保留的数据的应用程序非常有用。

esp_bt_controller_init()函数用于初始化蓝牙(Bluetooth)控制器,分配必要的任务和资源。
esp_bt_controller_enable函数用于启用蓝牙(Bluetooth)控制器。如果需要更改模式,必须先调用esp_bt_controller_disable()禁用控制器,然后使用新模式再次调用esp_bt_controller_enable()。
esp_bluedroid_init()函数用于初始化蓝牙堆栈,分配必要的资源。
esp_bluedroid_enable()函数用于启用Bluedroid蓝牙堆栈。
esp_ble_gatts_register_callback()函数用于注册GATT(Generic Attribute Profile)服务器回调函数。这个回调函数将会在GATT服务器事件发生时被调用,使得应用程序可以对不同的事件做出相应的处理。
esp_ble_gap_register_callback()函数用于注册全局的GAP(Generic Access Profile)回调函数。这个回调函数会在GAP相关事件发生时被调用,使得应用程序可以对不同的GAP事件做出相应的处理。
esp_ble_gatts_app_register()函数用于注册应用程序标识符(App ID)到GATT(Generic Attribute Profile)服务器。这是创建BLE GATT服务器应用程序的第一步,因为它允许应用程序在BLE框架中注册自己,以便接收GATT服务器相关的事件回调。
spp_task_init()函数用于创建一个名为`spp_cmd_task``的任务,用于处理蓝牙接收的数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
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;
}

3.2 蓝牙部分

3.2.1 gap部分

在gap回调函数中,可通过esp_gap_ble_cb_event_t结构体判断不同的事件类型进行相应的操作。
ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT事件中,设置蓝牙的广播间隔,使设备开始蓝牙广播。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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:
adv_int = 200;
spp_adv_params.adv_int_max = get_adv(200);
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;
}
}

esp_gap_ble_cb_event_t结构体如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
/// GAP BLE callback event type
typedef enum {
//BLE_42_FEATURE_SUPPORT
ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT = 0, /*!< When advertising data set complete, the event comes */
ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT, /*!< When scan response data set complete, the event comes */
ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT, /*!< When scan parameters set complete, the event comes */
ESP_GAP_BLE_SCAN_RESULT_EVT, /*!< When one scan result ready, the event comes each time */
ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT, /*!< When raw advertising data set complete, the event comes */
ESP_GAP_BLE_SCAN_RSP_DATA_RAW_SET_COMPLETE_EVT, /*!< When raw scan response data set complete, the event comes */
ESP_GAP_BLE_ADV_START_COMPLETE_EVT, /*!< When start advertising complete, the event comes */
ESP_GAP_BLE_SCAN_START_COMPLETE_EVT, /*!< When start scan complete, the event comes */
//BLE_INCLUDED
ESP_GAP_BLE_AUTH_CMPL_EVT = 8, /*!< Authentication complete indication. */
ESP_GAP_BLE_KEY_EVT, /*!< BLE key event for peer device keys */
ESP_GAP_BLE_SEC_REQ_EVT, /*!< BLE security request */
ESP_GAP_BLE_PASSKEY_NOTIF_EVT, /*!< passkey notification event */
ESP_GAP_BLE_PASSKEY_REQ_EVT, /*!< passkey request event */
ESP_GAP_BLE_OOB_REQ_EVT, /*!< OOB request event */
ESP_GAP_BLE_LOCAL_IR_EVT, /*!< BLE local IR (identity Root 128-bit random static value used to generate Long Term Key) event */
ESP_GAP_BLE_LOCAL_ER_EVT, /*!< BLE local ER (Encryption Root vakue used to genrate identity resolving key) event */
ESP_GAP_BLE_NC_REQ_EVT, /*!< Numeric Comparison request event */
//BLE_42_FEATURE_SUPPORT
ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT, /*!< When stop adv complete, the event comes */
ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT, /*!< When stop scan complete, the event comes */
//BLE_INCLUDED
ESP_GAP_BLE_SET_STATIC_RAND_ADDR_EVT = 19, /*!< When set the static rand address complete, the event comes */
ESP_GAP_BLE_UPDATE_CONN_PARAMS_EVT, /*!< When update connection parameters complete, the event comes */
ESP_GAP_BLE_SET_PKT_LENGTH_COMPLETE_EVT, /*!< When set pkt length complete, the event comes */
ESP_GAP_BLE_SET_LOCAL_PRIVACY_COMPLETE_EVT, /*!< When Enable/disable privacy on the local device complete, the event comes */
ESP_GAP_BLE_REMOVE_BOND_DEV_COMPLETE_EVT, /*!< When remove the bond device complete, the event comes */
ESP_GAP_BLE_CLEAR_BOND_DEV_COMPLETE_EVT, /*!< When clear the bond device clear complete, the event comes */
ESP_GAP_BLE_GET_BOND_DEV_COMPLETE_EVT, /*!< When get the bond device list complete, the event comes */
ESP_GAP_BLE_READ_RSSI_COMPLETE_EVT, /*!< When read the rssi complete, the event comes */
ESP_GAP_BLE_UPDATE_WHITELIST_COMPLETE_EVT, /*!< When add or remove whitelist complete, the event comes */
//BLE_42_FEATURE_SUPPORT
ESP_GAP_BLE_UPDATE_DUPLICATE_EXCEPTIONAL_LIST_COMPLETE_EVT, /*!< When update duplicate exceptional list complete, the event comes */
//BLE_INCLUDED
ESP_GAP_BLE_SET_CHANNELS_EVT = 29, /*!< When setting BLE channels complete, the event comes */
//BLE_50_FEATURE_SUPPORT
ESP_GAP_BLE_READ_PHY_COMPLETE_EVT, /*!< when reading phy complete, this event comes */
ESP_GAP_BLE_SET_PREFERRED_DEFAULT_PHY_COMPLETE_EVT, /*!< when preferred default phy complete, this event comes */
ESP_GAP_BLE_SET_PREFERRED_PHY_COMPLETE_EVT, /*!< when preferred phy complete , this event comes */
ESP_GAP_BLE_EXT_ADV_SET_RAND_ADDR_COMPLETE_EVT, /*!< when extended set random address complete, the event comes */
ESP_GAP_BLE_EXT_ADV_SET_PARAMS_COMPLETE_EVT, /*!< when extended advertising parameter complete, the event comes */
ESP_GAP_BLE_EXT_ADV_DATA_SET_COMPLETE_EVT, /*!< when extended advertising data complete, the event comes */
ESP_GAP_BLE_EXT_SCAN_RSP_DATA_SET_COMPLETE_EVT, /*!< when extended scan response data complete, the event comes */
ESP_GAP_BLE_EXT_ADV_START_COMPLETE_EVT, /*!< when extended advertising start complete, the event comes */
ESP_GAP_BLE_EXT_ADV_STOP_COMPLETE_EVT, /*!< when extended advertising stop complete, the event comes */
ESP_GAP_BLE_EXT_ADV_SET_REMOVE_COMPLETE_EVT, /*!< when extended advertising set remove complete, the event comes */
ESP_GAP_BLE_EXT_ADV_SET_CLEAR_COMPLETE_EVT, /*!< when extended advertising set clear complete, the event comes */
ESP_GAP_BLE_PERIODIC_ADV_SET_PARAMS_COMPLETE_EVT, /*!< when periodic advertising parameter complete, the event comes */
ESP_GAP_BLE_PERIODIC_ADV_DATA_SET_COMPLETE_EVT, /*!< when periodic advertising data complete, the event comes */
ESP_GAP_BLE_PERIODIC_ADV_START_COMPLETE_EVT, /*!< when periodic advertising start complete, the event comes */
ESP_GAP_BLE_PERIODIC_ADV_STOP_COMPLETE_EVT, /*!< when periodic advertising stop complete, the event comes */
ESP_GAP_BLE_PERIODIC_ADV_CREATE_SYNC_COMPLETE_EVT, /*!< when periodic advertising create sync complete, the event comes */
ESP_GAP_BLE_PERIODIC_ADV_SYNC_CANCEL_COMPLETE_EVT, /*!< when extended advertising sync cancel complete, the event comes */
ESP_GAP_BLE_PERIODIC_ADV_SYNC_TERMINATE_COMPLETE_EVT, /*!< when extended advertising sync terminate complete, the event comes */
ESP_GAP_BLE_PERIODIC_ADV_ADD_DEV_COMPLETE_EVT, /*!< when extended advertising add device complete , the event comes */
ESP_GAP_BLE_PERIODIC_ADV_REMOVE_DEV_COMPLETE_EVT, /*!< when extended advertising remove device complete, the event comes */
ESP_GAP_BLE_PERIODIC_ADV_CLEAR_DEV_COMPLETE_EVT, /*!< when extended advertising clear device, the event comes */
ESP_GAP_BLE_SET_EXT_SCAN_PARAMS_COMPLETE_EVT, /*!< when extended scan parameter complete, the event comes */
ESP_GAP_BLE_EXT_SCAN_START_COMPLETE_EVT, /*!< when extended scan start complete, the event comes */
ESP_GAP_BLE_EXT_SCAN_STOP_COMPLETE_EVT, /*!< when extended scan stop complete, the event comes */
ESP_GAP_BLE_PREFER_EXT_CONN_PARAMS_SET_COMPLETE_EVT, /*!< when extended prefer connection parameter set complete, the event comes */
ESP_GAP_BLE_PHY_UPDATE_COMPLETE_EVT, /*!< when ble phy update complete, the event comes */
ESP_GAP_BLE_EXT_ADV_REPORT_EVT, /*!< when extended advertising report complete, the event comes */
ESP_GAP_BLE_SCAN_TIMEOUT_EVT, /*!< when scan timeout complete, the event comes */
ESP_GAP_BLE_ADV_TERMINATED_EVT, /*!< when advertising terminate data complete, the event comes */
ESP_GAP_BLE_SCAN_REQ_RECEIVED_EVT, /*!< when scan req received complete, the event comes */
ESP_GAP_BLE_CHANNEL_SELECT_ALGORITHM_EVT, /*!< when channel select algorithm complete, the event comes */
ESP_GAP_BLE_PERIODIC_ADV_REPORT_EVT, /*!< when periodic report advertising complete, the event comes */
ESP_GAP_BLE_PERIODIC_ADV_SYNC_LOST_EVT, /*!< when periodic advertising sync lost complete, the event comes */
ESP_GAP_BLE_PERIODIC_ADV_SYNC_ESTAB_EVT, /*!< when periodic advertising sync establish complete, the event comes */
//BLE_INCLUDED
ESP_GAP_BLE_SC_OOB_REQ_EVT, /*!< Secure Connection OOB request event */
ESP_GAP_BLE_SC_CR_LOC_OOB_EVT, /*!< Secure Connection create OOB data complete event */
ESP_GAP_BLE_GET_DEV_NAME_COMPLETE_EVT, /*!< When getting BT device name complete, the event comes */
//BLE_FEAT_PERIODIC_ADV_SYNC_TRANSFER
ESP_GAP_BLE_PERIODIC_ADV_RECV_ENABLE_COMPLETE_EVT, /*!< when set periodic advertising receive enable complete, the event comes */
ESP_GAP_BLE_PERIODIC_ADV_SYNC_TRANS_COMPLETE_EVT, /*!< when periodic advertising sync transfer complete, the event comes */
ESP_GAP_BLE_PERIODIC_ADV_SET_INFO_TRANS_COMPLETE_EVT, /*!< when periodic advertising set info transfer complete, the event comes */
ESP_GAP_BLE_SET_PAST_PARAMS_COMPLETE_EVT, /*!< when set periodic advertising sync transfer params complete, the event comes */
ESP_GAP_BLE_PERIODIC_ADV_SYNC_TRANS_RECV_EVT, /*!< when periodic advertising sync transfer received, the event comes */
ESP_GAP_BLE_EVT_MAX, /*!< when maximum advertising event complete, the event comes */
} esp_gap_ble_cb_event_t;

3.2.2 gatt部分

在gatt回调函数中,可通过esp_gatts_cb_event_t结构体判断不同的事件类型进行相应的操作。
ESP_GATTS_REG_EVT事件中,设置蓝牙广播包与蓝牙扫描回应包的数据。
esp_ble_gap_config_adv_data_raw()函数用于设置原始广播数据。这里将广播数据设置为iBeacon广播。
esp_ble_gap_config_scan_rsp_data_raw()函数用于设置原始扫描响应(Scan Response)数据。这里将扫描回应包的数据设置为广播名称。

在蓝牙低功耗(Bluetooth Low Energy, BLE)通信中,广播数据包(Advertising Packet)和扫描回应包(Scan Response Packet)是两种基本的数据包类型,用于设备发现和信息交换过程中。这些包的设计使得BLE设备能够有效地传输少量信息,同时保持低功耗特性。
广播数据包(Advertising Packet)
广播数据包是BLE设备用来向周围环境宣告其存在的一种数据包。广播设备(广播者)定期发送这些包,以便监听设备(扫描者)可以检测到它们并采取进一步的行动,如发起连接或查询更多信息。
扫描回应包(Scan Response Packet)
扫描回应包是在BLE设备收到扫描请求包后发送的。当一个设备在广播时,另一个设备可以发送一个扫描请求来要求更多的信息。这时,广播设备会回应一个扫描回应包,包含额外的信息。

扫描回应包的结构与广播数据包类似,也是最大31字节的数据,可以包含设备名称、服务UUID等信息。这允许设备在不建立完整的BLE连接的情况下,提供更多的信息。
ESP_GATTS_WRITE_EVT事件中,接收对端蓝牙设备发送过来的数据。
xQueueSend()函数用于向队列发送(或“发送”)数据。这个函数将数据项复制到队列中,而不是通过引用传递。如果队列已满,调用该函数的任务可以根据指定的等待时间(以时钟滴答为单位)决定是否阻塞,直到队列中有空间为止。

ESP_GATTS_DISCONNECT_EVT事件中,当设备断开连接时,更新spp_adv_params.adv_int_max参数,使设备重新开始广播。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
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_ble_ibeacon_t ibeacon_adv_data;

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_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(GATTS_TABLE_TAG, "Config iBeacon data failed: %s\n", esp_err_to_name(status));
}
// esp_ble_gap_config_adv_data_raw((uint8_t *)spp_adv_data, sizeof(spp_adv_data));
esp_ble_gap_config_scan_rsp_data_raw((uint8_t *)scan_adv_data, sizeof(scan_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
spp_adv_params.adv_int_max = get_adv(adv_int);
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;
}
}

esp_gatts_cb_event_t结构体如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/// GATT Server callback function events
typedef enum {
ESP_GATTS_REG_EVT = 0, /*!< When register application id, the event comes */
ESP_GATTS_READ_EVT = 1, /*!< When gatt client request read operation, the event comes */
ESP_GATTS_WRITE_EVT = 2, /*!< When gatt client request write operation, the event comes */
ESP_GATTS_EXEC_WRITE_EVT = 3, /*!< When gatt client request execute write, the event comes */
ESP_GATTS_MTU_EVT = 4, /*!< When set mtu complete, the event comes */
ESP_GATTS_CONF_EVT = 5, /*!< When receive confirm, the event comes */
ESP_GATTS_UNREG_EVT = 6, /*!< When unregister application id, the event comes */
ESP_GATTS_CREATE_EVT = 7, /*!< When create service complete, the event comes */
ESP_GATTS_ADD_INCL_SRVC_EVT = 8, /*!< When add included service complete, the event comes */
ESP_GATTS_ADD_CHAR_EVT = 9, /*!< When add characteristic complete, the event comes */
ESP_GATTS_ADD_CHAR_DESCR_EVT = 10, /*!< When add descriptor complete, the event comes */
ESP_GATTS_DELETE_EVT = 11, /*!< When delete service complete, the event comes */
ESP_GATTS_START_EVT = 12, /*!< When start service complete, the event comes */
ESP_GATTS_STOP_EVT = 13, /*!< When stop service complete, the event comes */
ESP_GATTS_CONNECT_EVT = 14, /*!< When gatt client connect, the event comes */
ESP_GATTS_DISCONNECT_EVT = 15, /*!< When gatt client disconnect, the event comes */
ESP_GATTS_OPEN_EVT = 16, /*!< When connect to peer, the event comes */
ESP_GATTS_CANCEL_OPEN_EVT = 17, /*!< When disconnect from peer, the event comes */
ESP_GATTS_CLOSE_EVT = 18, /*!< When gatt server close, the event comes */
ESP_GATTS_LISTEN_EVT = 19, /*!< When gatt listen to be connected the event comes */
ESP_GATTS_CONGEST_EVT = 20, /*!< When congest happen, the event comes */
/* following is extra event */
ESP_GATTS_RESPONSE_EVT = 21, /*!< When gatt send response complete, the event comes */
ESP_GATTS_CREAT_ATTR_TAB_EVT = 22, /*!< When gatt create table complete, the event comes */
ESP_GATTS_SET_ATTR_VAL_EVT = 23, /*!< When gatt set attr value complete, the event comes */
ESP_GATTS_SEND_SERVICE_CHANGE_EVT = 24, /*!< When gatt send service change indication complete, the event comes */
} esp_gatts_cb_event_t;

3.2.3 数据处理部分

在主函数中,已调用spp_task_init()函数初始化创建spp_cmd_task任务。在spp_cmd_task任务中,使用xQueueReceive()函数接收蓝牙数据进行处理。在这里,我们使用nrf connect连接设备,发送20-10240之间的数字即可更新设备的广播间隔。蓝牙设备的广播间隔在20ms10.24s之间,广播间隔越长,越难扫描到设备。
此处,可以对指令进行识别,从而达到控制设备不同参数的目的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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)) {
adv_int = atoi((char *)cmd_id);
esp_log_buffer_char(GATTS_TABLE_TAG,(char *)(cmd_id),strlen((char *)cmd_id));
free(cmd_id);
}
}
vTaskDelete(NULL);
}

四、链接

Github仓库:ibeacon