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;
添加描述符后,将ESP_GATTS_ADD_CHAR_DESCR_EVT
触发事件,在此示例中用于打印信息消息。
case ESP_GATTS_ADD_CHAR_DESCR_EVT:
ESP_LOGI(GATTS_TAG, "ADD_DESCR_EVT, status %d, attr_handle %d, service_handle %d\n",
param->add_char.status, param->add_char.attr_handle,
param->add_char.service_handle);
break;
一个ESP_GATTS_CONNECT_EVT
当客户端已连接到服务器GATT被触发。此事件用于更新连接参数,例如延迟、最小连接间隔、最大连接间隔和超时。连接参数存储在一个esp_ble_conn_update_params_t
结构中,然后传递给esp_ble_gap_update_conn_params()
函数。更新连接参数过程只需执行一次,因此配置文件 B 连接事件处理程序不包含该esp_ble_gap_update_conn_params()
函数。最后,事件返回的连接 ID 存储在配置文件表中。
配置文件 A 连接事件:
case ESP_GATTS_CONNECT_EVT: {
esp_ble_conn_update_params_t conn_params = {0};
memcpy(conn_params.bda, param->connect.remote_bda, sizeof(esp_bd_addr_t));
conn_params.latency = 0;
conn_params.max_int = 0x30;
conn_params.min_int = 0x10;
conn_params.timeout = 400;
ESP_LOGI(GATTS_TAG, "ESP_GATTS_CONNECT_EVT, conn_id %d, remote %02x:%02x:%02x:%02x:%02x:%02x:, is_conn %d",
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],
param->connect.is_connected);
gl_profile_tab[PROFILE_A_APP_ID].conn_id = param->connect.conn_id;
esp_ble_gap_update_conn_params(&conn_params);
break;
配置文件 B 连接事件:
case ESP_GATTS_CONNECT_EVT:
ESP_LOGI(GATTS_TAG, "CONNECT_EVT, conn_id %d, remote %02x:%02x:%02x:%02x:%02x:%02x:, is_conn %d\n",
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],
param->connect.is_connected);
gl_profile_tab[PROFILE_B_APP_ID].conn_id = param->connect.conn_id;
break;
该esp_ble_gap_update_conn_params()
函数触发一个 GAP 事件ESP_GAP_BLE_UPDATE_CONN_PARAMS_EVT
,用于打印连接信息:
case ESP_GAP_BLE_UPDATE_CONN_PARAMS_EVT:
ESP_LOGI(GATTS_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;
当有手机(client客户端)连上server时,触发ESP_GATTS_MTU_EVT事件,其打印如下图所示
ESP_GATTS_MTU_EVT事件对应的回调函数中参数param的结构体为gatts_mtu_evt_param(包括连接id和MTU大小)
* @brief ESP_GATTS_MTU_EVT
struct gatts_mtu_evt_param {
uint16_t conn_id;
uint16_t mtu;
} mtu;
在例子中设置本地的MTU大小为500,代码如下所示:
esp_err_t local_mtu_ret = esp_ble_gatt_set_local_mtu(500);
如上所述,设置了MTU的值(经过MTU交换,从而设置一个PDU中最大能够交换的数据量)。例如:主设备发出一个150字节的MTU请求,但是从设备回应的MTU是23字节,那么今后双方要以较小的值23字节作为以后的MTU。即主从双方每次在做数据传输时不超过这个最大数据单元。 MTU交换通常发生在主从双方建立连接后。MTU比较小,就是为什么BLE不能传输大数据的原因所在。
参照一分钟读懂低功耗(BLE)MTU交换数据包 这篇文章就可以了解MTU交换过程。
MTU交换请求用于client通知server关于client最大接收MTU大小并请求server响应它的最大接收MTU大小。
Client的接收MTU 应该大于或等于默认ATT_MTU(23).这个请求已建立连接就由client发出。这个Client Rx MTU参数应该设置为client可以接收的attribute protocol PDU最大尺寸。
MTU交换应答发送用于接收到一个Exchange MTU请求
这个应答由server发出,server的接收MTU必须大于或等于默认ATT_MTU大小。这里的Server Rx MTU应该设置为 服务器可以接收的attribute protocol PDU 最大尺寸。
Server和Client应该设置ATT_MTU为Client Rx MTU和Server Rx MTU两者的较小值。
这个ATT_MTU在server在发出这个应答后,在发其他属性协议PDU之前生效;在client收到这个应答并在发其他属性协议PDU之前生效。
现在已经创建并启动了服务和特征,程序可以接收读写事件。读取操作由ESP_GATTS_READ_EVT
事件表示,它具有以下参数:
uint16_t conn_id;
uint32_t trans_id;
esp_bd_addr_t bda;
uint16_t handle;
uint16_t offset;
bool is_long;
bool need_rsp;
demo中,响应是用虚拟数据构造的,并使用事件给定的相同句柄发送回主机。除了响应之外,GATT 接口、连接 ID 和传输 ID 也作为参数包含在esp_ble_gatts_send_response()
函数中。如果在创建特征或描述符时将自动响应字节设置为 NULL,则此功能是必需的。
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;
写入事件由事件表示ESP_GATTS_WRITE_EVT
,它具有以下参数:
uint16_t conn_id;
uint32_t trans_id;
esp_bd_addr_t bda;
uint16_t handle;
uint16_t offset;
bool need_rsp;
bool is_prep;
uint16_t len;
uint8_t *value;
demo中实现了两种类型的写事件,写特征值和写长特征值。当特征值可以容纳在一个属性协议最大传输单元 (ATT MTU) 中时,使用第一种类型的写入,该单元通常为 23 字节长。当要写入的属性长于单个 ATT 消息中可以发送的属性时使用第二种类型,通过使用准备写入响应将数据分成多个块,然后使用执行写入请求来确认或取消完整的写入请求. 此行为在蓝牙规范版本 4.2,第 3 卷,G 部分,第 4.9 节中定义。写长特征消息流如下图所示。
当触发写入事件时,此示例打印日志消息,然后执行example_write_event_env()
函数。
case ESP_GATTS_WRITE_EVT: {
ESP_LOGI(GATTS_TAG, "GATT_WRITE_EVT, conn_id %d, trans_id %d, handle %d\n", 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_B_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 (b_property & ESP_GATT_CHAR_PROP_BIT_NOTIFY){
ESP_LOGI(GATTS_TAG, "notify enable");
uint8_t notify_data[15];
for (int i = 0; i < sizeof(notify_data); ++i)
notify_data[i] = i%0xff;
esp_ble_gatts_send_indicate(gatts_if, param->write.conn_id,
gl_profile_tab[PROFILE_B_APP_ID].char_handle,
sizeof(notify_data),
notify_data, false);
}else if (descr_value == 0x0002){
if (b_property & ESP_GATT_CHAR_PROP_BIT_INDICATE){
ESP_LOGI(GATTS_TAG, "indicate enable");
uint8_t indicate_data[15];
for (int i = 0; i < sizeof(indicate_data); ++i)
indicate_data[i] = i % 0xff;
esp_ble_gatts_send_indicate(gatts_if, param->write.conn_id,
gl_profile_tab[PROFILE_B_APP_ID].char_handle,
sizeof(indicate_data),
indicate_data, true);
else if (descr_value == 0x0000){
ESP_LOGI(GATTS_TAG, "notify/indicate disable ");
}else{
ESP_LOGE(GATTS_TAG, "unknown value");
example_write_event_env(gatts_if, &a_prepare_write_env, param);
break;
该example_write_event_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){
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) {
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);
当客户端发送写请求或准备写请求时,服务器应响应。但是,如果客户端发送 Write without Response 命令,则服务器不需要回复响应。这是在写入过程中通过检查 的值来检查的write.need_rsp parameter
。如果需要响应,程序继续做响应准备,如果不存在,客户端不需要响应,因此程序结束。响应的话会影响数据传输速度,在需要大数据量的场合是否合适需要试验?
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){
然后该函数检查是否write.is_prep
设置了由 表示的 Prepare Write Request 参数,这意味着客户端正在请求 Write Long Characteristic。如果存在,该过程继续准备多个写响应,如果不存在,则服务器简单地发回单个写响应。
if (param->write.is_prep){
}else{
esp_ble_gatts_send_response(gatts_if, param->write.conn_id, param->write.trans_id, status, NULL);
为了处理长特征写入,定义并实例化了一个准备缓冲区结构:
typedef struct {
uint8_t *prepare_buf;
int prepare_len;
} prepare_type_env_t;
static prepare_type_env_t a_prepare_write_env;
static prepare_type_env_t b_prepare_write_env;
为了使用准备缓冲区,为其分配了一些内存空间。如果由于内存不足导致分配失败,则会打印错误:
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;
该过程现在准备esp_gatt_rsp_t
要发送回客户端的类型响应。它使用写入请求的相同参数构造的响应,例如长度、句柄和偏移量。另外,写入该特性所需的GATT认证类型设置为ESP_GATT_AUTH_REQ_NONE
,这意味着客户端可以写入该特性而无需先进行身份验证。一旦发送响应,分配给它使用的内存就会被释放。
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;
最后,传入的数据被复制到创建的缓冲区中,其长度按偏移量递增:
case ESP_GATTS_EXEC_WRITE_EVT:
ESP_LOGI(GATTS_TAG,"ESP_GATTS_EXEC_WRITE_EVT");
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;
我们来看看Executive Write函数:
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;
执行写入用于确认或取消之前完成的写入过程,由长特征写入过程。为此,该函数会检查exec_write_flag
随事件接收到的参数中的 。如果标志等于 表示的执行标志exec_write_flag
,则确认写入并在日志中打印缓冲区;如果不是,则表示取消写入并删除所有已写入的数据。
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");
最后,为存储来自长写操作的数据块而创建的缓冲区结构被释放,并将其指针设置为 NULL 以使其为下一个长写过程做好准备。
if (prepare_write_env->prepare_buf) {
free(prepare_write_env->prepare_buf);
prepare_write_env->prepare_buf = NULL;
prepare_write_env->prepare_len = 0;
使能notify并读取蓝牙发过来的数据,开启这个后我们就能实时获取蓝牙发过来的值了。
使能通知(notify enable)的打印如下所示,打开通知实际上的一个WRITE。
如果write.handle和descr_handle相同,且长度==2,确定descr_value描述值,根据描述值开启/关闭 通知notify/indicate。
esp_ble_gatts_send_indicate(gatts_if, param->write.conn_id, gl_profile_tab[PROFILE_A_APP_ID].char_handle,
sizeof(notify_data), notify_data, false);
该函数将notify或indicate发给GATT的客户端;
need_confirm = false,则发送的是notification通知;
==true,发送的是指示indication。
其他参数: 服务端访问接口;连接id; 属性句柄,value_len; 值
• 由 Leung 写于 2021 年 7 月 7 日
• 参考:ESP32学习笔记(7)蓝牙GATT服务应用
Gatt 服务器示例演练
一、简介1.1 低功耗蓝牙(BLE)协议栈链路层(LL) 控制设备的射频状态,有五个设备状态:待机、广播、扫描、初始化和连接。广播 为广播数据包,而 扫描 则是监听广播。GAP通信中角色,中心设备(Central - 主机) 用来扫描和连接 外围设备(Peripheral - 从机)。大部分情况下外围设备通过广播自己来让中心设备发现自己,并建立 GATT 连接,从而进行更多的数据交换。也有些情况是不需要连接的,只要外设广播自己的数据即可,用这种方式主要目的是让外围设备,把自己的信息发送给多个中
ESP32-BLE2MQTT
该项目是BLE到MQTT的桥梁,即它将BLE GATT特性作为MQTT主题公开,用于双向通信。 它是为ESP32 SoC开发的,基于版本v4.1。 请注意,使用任何其他ESP-IDF版本可能不稳定,甚至无法编译。
例如,如果MAC地址为a0:e6:f8:50:72:53公开了(电池服务),其中包括(电池电量)时,将发布a0:e6:f8:50:72:53/BatteryService/BatteryLevel MQTT主题,其中的值代表电池电量。
支持通知的特征将自动注册,并在可用时发布新值。 通过使用后缀为“ / Get”的上述格式将任何值发布到主题,也可以主动发出读取请求。 请注意,值是基于从的定义表示特征值的字符串。 例如,电池电量为100%(0x64)将作为字符串'100'发送。
为了设置GATT值,请使用后缀/Set的上述格式将消息发布到可写特
1 在 Eclipse 界面启动MenuConfigTarget (需参照如何使用安信可 ESP 系列一体化开发环境IDF章节);
2 打开 Cygwin.bat, 进入工程目录,执行make menuconfig指令。
这 2 种方法最终实现的效果都是一致的,您可以根据自己的需要进行选择。
执行make menuconfig后,界面显示如下:
SDK tool configurati...
esp32 ble供用户使用的只有31字节。这个31字节需按照蓝牙广播包中的ad格式:len + type 。其中esp32有两种方式设置广播包:一种是通过esp_ble_adv_data_t结构体,使用接口api,设置广播数据;另一种是通过uint8_t的数据流写进入广播包,但是两者都不能超过31字节。如果用户自定义广播包需要超过31字节,就需要进行广播分包设置。
esp32广播包分包设置支持:通过先发送第一个广播消息包(例如:原始数据ra
遇到滑块问题
在写爬虫的时候,经常会遇到滑块问题,很多次都想过尝试如何攻破滑块,但是每次都没成功,除了最开始的极验滑块,当时通过原图和滑块图的对比,能够得出缺口坐标,但是随着极验、网易、腾讯滑块的更新,已经不能够找到原图了,下面给出滑块通杀的解决方案。
尝试攻破滑块
在这里介绍一款通杀滑块的平台,不过需要开通VIP,VIP是永久的,可以无限次识别,我在这里开通了永久VIP,花了99RMB,平台后面也会推出点选供VIP使用。
平台地址:www.51learn.vip
网站名称是【无限打码】,可以直接百度到。
1.1 通用属性协议(GATT)
GATT是用Attribute Protocal(属性协议)定义的一个service(服务)框架。这个框架定义了Services以及它们的Characteristics的格式和规程。规程就是定义了包括发现、读、写、通知、指示以及配置广播的characteristics。
为实现配置文件(Profile)的设备定义了两种角色:Client(客户端)、Server(服务器)。esp32的ble一般就处于Server模式。
一旦两个设备建立了连接,GATT就开始发挥效用
ESP_GATTS_REG_EVT -------------------------- 注册
ESP_GATTS_CREATE_EVT --------------------- 创建
ESP_GATTS_START_EVT ----------------------- 启动
ESP_GATTS_ADD_CHAR_EVT ---------------- 添加特
case ESP_GAP_SEARCH_INQ_CMPL_EVT:
ESP_LOGI(HCI_TAG, "X ESP_GAP_SEARCH_INQ_CMPL_EVT 程序会截至到这里 表示扫描的时间到了 一个满足条件的从机都没有扫到");
esp_ble_gap_start_scanning(10);//我再次开始扫描 这里是10S 使用串口工具看 就是实打实的10S 如果还是没有满足条件的从机
写爬虫遇到验证码识别问题的解决方案
遇到滑块问题
在写爬虫的时候,经常会遇到滑块问题,很多次都想过尝试如何攻破滑块,但是每次都没成功,除了最开始的极验滑块,当时通过原图和滑块图的对比,能够得出缺口坐标,但是随着极验、网易、腾讯滑块的更新,已经不能够找到原图了,下面给出滑块通杀的解决方案。
尝试攻破滑块
在这里介绍一款通杀滑块的平台,不过需要开通VIP,VIP是永久的,可以无限次识别,我在这里开通了永久VIP,花了99RMB,也可以包月,平台后面也会推出点选供VIP使用。
平台地址:www.51learn.v
链路层(LL) 控制设备的射频状态,有五个设备状态:待机、广播、扫描、初始化和连接。
广播 为广播数据包,而 扫描 则是监听广播。
GAP通信中角色,中心设备(Central - 主机) 用来扫描和连接 外围设备(Peripheral - 从机)。
大部分情况下外围设备通过广播自己来让中心设备发现自己,并建立 GATT 连接,从而进行更多的数据交换。
也有些情况是不需要连接的,只要外设广播自己的数据即可,用这种方式主要目的是让外围设备,把自己的信息发送给多个中
ESP32可以通过BLE协议实现文件传输,一般可以按照以下步骤进行:
1. 建立BLE连接:使用ESP32作为BLE服务器,通过GATT协议和BLE客户端建立连接。
2. 发送数据:BLE连接建立后,可以向BLE客户端发送文件数据,可以使用BLE通知属性(Notification)或者BLE指示属性(Indication)实现数据发送。
3. 接收数据:BLE客户端可以接收ESP32发送的数据,可以使用BLE读属性(Read)或者BLE写属性(Write)实现数据接收。
4. 文件重组:接收到的文件数据可能会被分为多个数据包发送,需要在BLE客户端进行文件重组,将所有数据包组合成完整的文件。
5. 文件存储:将重组后的文件存储到BLE客户端的本地存储器中,也可以将文件数据通过BLE连接发送到云端或者其他设备。
需要注意的是,ESP32的BLE传输速度相对较慢,且BLE连接在传输过程中会受到其他无线信号的干扰,因此在进行文件传输时需要考虑数据安全性和传输效率。
解决方法:Keil提示错误信息error: L6235E: More than one section matches selector - cannot all be FIRST/LAST.
zqinnuaa:
STM32CubeMX学习笔记(43)——USB接口使用(CDC虚拟串口)
午觉睡到四点半:
NRF52832学习笔记(9)——GAP从机端广播
FNOI06Bcybo:
STM32CubeMX学习笔记(30)——FreeRTOS实时操作系统使用(信号量)
C51shit:
STM32CubeMX学习笔记(30)——FreeRTOS实时操作系统使用(信号量)
C51shit: