#include #include #include #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "esp_log.h" #include "esp_bt.h" #include "esp_gap_ble_api.h" #include "esp_gatts_api.h" #include "esp_bt_main.h" #include "esp_bt_device.h" #include "esp_gatt_common_api.h" #include "esp_mac.h" #include "fatfs.h" #include "pages.h" #define APP_ID_PLACEHOLDER 0 #define IMAGE_SERVICE_INSTID 0x0B #define IMAGE_SERVICE_UUID 0x0B00 #define IMAGE_WRITE_UUID 0x0B01 #define IMAGE_EDIT_UUID 0x0B02 static uint16_t image_service_handle = 0; static uint16_t image_write_handle = 0; static uint16_t image_edit_handle = 0; static void esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param); static void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param); static const char *CONN_TAG = "DZBJ_BLE"; static char ble_device_name[32]; static uint8_t adv_raw_len = 0; static uint16_t conn_id; static char *filepath; typedef struct { uint8_t type; char filename[23]; uint32_t len; } Megtype; typedef struct{ bool isSend; uint32_t port; } MegStatus; Megtype firstMeg; MegStatus SendStatus = {false,0}; uint8_t *img_data = 0; FILE *file_img; // BLE 图片处理任务(NVS 写入 + 导航显示在独立任务中执行,避免 BTC_TASK 栈溢出) static TaskHandle_t ble_process_task_handle = NULL; static char ble_pending_filename[24]; static uint8_t *ble_pending_data = NULL; // 传输完成的图片数据(直通显示,跳过 SPIFFS 重读) static size_t ble_pending_data_size = 0; static void ble_process_task(void *arg) { while (1) { ulTaskNotifyTake(pdTRUE, portMAX_DELAY); nvs_change_img(ble_pending_filename); ble_image_navigate_with_data(ble_pending_filename, ble_pending_data, ble_pending_data_size); ble_pending_data = NULL; // 所有权已转移,不再释放 ble_pending_data_size = 0; } } static uint8_t attr_value_write[512] = {0}; static uint8_t attr_value_edit[20] = {0}; static esp_attr_value_t char_val_image_write = { .attr_max_len = 512, .attr_len = 512, .attr_value = attr_value_write } ; static esp_attr_value_t char_val_image_edit = { .attr_max_len = 20, .attr_len = 20, .attr_value = attr_value_edit } ; static esp_attr_control_t control_image_write = { .auto_rsp = ESP_GATT_AUTO_RSP }; static esp_attr_control_t control_image_edit = { .auto_rsp = ESP_GATT_AUTO_RSP }; // 图片传输服务 static esp_gatt_srvc_id_t server_id_image = { .id.uuid.len = ESP_UUID_LEN_16, .id.uuid.uuid.uuid16 = IMAGE_SERVICE_UUID, .id.inst_id = IMAGE_SERVICE_INSTID, .is_primary = true, }; static esp_bt_uuid_t image_write_uuid = { .len = ESP_UUID_LEN_16, .uuid.uuid16 = IMAGE_WRITE_UUID, }; static esp_bt_uuid_t image_edit_uuid = { .len = ESP_UUID_LEN_16, .uuid.uuid16 = IMAGE_EDIT_UUID, }; static esp_ble_adv_params_t adv_params = { .adv_int_min = 0x20, .adv_int_max = 0x20, .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, }; static uint8_t adv_raw_data[31]; // Scan Response 数据:厂商标识 + 服务UUID static uint8_t scan_rsp_data[] = { 0x07, ESP_BLE_AD_MANUFACTURER_SPECIFIC_TYPE, 0x4C, 0x44, 0x64, 0x7A, 0x62, 0x6A, // "LDdzbj" 0x03, ESP_BLE_AD_TYPE_16SRV_CMPL, 0x00, 0x0B, // 服务UUID 0x0B00 }; void dzbj_ble_init(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(); ret = esp_bt_controller_init(&bt_cfg); if (ret) { ESP_LOGE(CONN_TAG, "%s initialize controller failed: %s", __func__, esp_err_to_name(ret)); return; } ret = esp_bt_controller_enable(ESP_BT_MODE_BLE); if (ret) { ESP_LOGE(CONN_TAG, "%s enable controller failed: %s", __func__, esp_err_to_name(ret)); return; } ret = esp_bluedroid_init(); if (ret) { ESP_LOGE(CONN_TAG, "%s init bluetooth failed: %s", __func__, esp_err_to_name(ret)); return; } ret = esp_bluedroid_enable(); if (ret) { ESP_LOGE(CONN_TAG, "%s enable bluetooth failed: %s", __func__, esp_err_to_name(ret)); return; } ret = esp_ble_gap_register_callback(esp_gap_cb); if (ret) { ESP_LOGE(CONN_TAG, "%s gap register failed, error code = %x", __func__, ret); return; } ret = esp_ble_gatts_register_callback(gatts_event_handler); if (ret) { ESP_LOGE(CONN_TAG, "%s gatts register failed, error code = %x", __func__, ret); return; } ret = esp_ble_gatts_app_register(APP_ID_PLACEHOLDER); if (ret) { ESP_LOGE(CONN_TAG, "%s gatts app register failed, error code = %x", __func__, ret); return; } ret = esp_ble_gatt_set_local_mtu(512); if (ret) { ESP_LOGE(CONN_TAG, "set local MTU failed, error code = %x", ret); return; } // 获取 BLE MAC 地址并构建设备名称: Airhub_xx:xx:xx:xx:xx:xx const uint8_t *ble_addr = esp_bt_dev_get_address(); if (ble_addr) { snprintf(ble_device_name, sizeof(ble_device_name), "Airhub_%02x:%02x:%02x:%02x:%02x:%02x", ble_addr[0], ble_addr[1], ble_addr[2], ble_addr[3], ble_addr[4], ble_addr[5]); ESP_LOGI(CONN_TAG, "BLE MAC: %02x:%02x:%02x:%02x:%02x:%02x", ble_addr[0], ble_addr[1], ble_addr[2], ble_addr[3], ble_addr[4], ble_addr[5]); } else { strcpy(ble_device_name, "Airhub_BLE"); ESP_LOGW(CONN_TAG, "获取BLE MAC失败,使用默认名称: %s", ble_device_name); } ret = esp_ble_gap_set_device_name(ble_device_name); if (ret) { ESP_LOGE(CONN_TAG, "set device name failed, error code = %x", ret); return; } ESP_LOGI(CONN_TAG, "蓝牙设备名称: %s", ble_device_name); // 构建广播数据: Flags + Complete Local Name uint8_t name_len = strlen(ble_device_name); int offset = 0; adv_raw_data[offset++] = 0x02; adv_raw_data[offset++] = ESP_BLE_AD_TYPE_FLAG; adv_raw_data[offset++] = 0x06; adv_raw_data[offset++] = name_len + 1; adv_raw_data[offset++] = ESP_BLE_AD_TYPE_NAME_CMPL; memcpy(&adv_raw_data[offset], ble_device_name, name_len); offset += name_len; adv_raw_len = offset; ret = esp_ble_gap_config_adv_data_raw(adv_raw_data, adv_raw_len); if (ret) { ESP_LOGE(CONN_TAG, "config adv data failed, error code = %x", ret); } // 配置 Scan Response 数据(厂商标识 "dzbj" + 服务UUID) ret = esp_ble_gap_config_scan_rsp_data_raw(scan_rsp_data, sizeof(scan_rsp_data)); if (ret) { ESP_LOGE(CONN_TAG, "config scan response data failed, error code = %x", ret); } // 创建图片处理任务(8KB 栈,足够 SPIFFS 扫描 + LVGL + GIF 解码) xTaskCreate(ble_process_task, "ble_img", 8192, NULL, 5, &ble_process_task_handle); } void dzbj_ble_deinit(void) { esp_ble_gap_stop_advertising(); esp_ble_gatts_app_unregister(0); esp_bluedroid_disable(); esp_bluedroid_deinit(); esp_bt_controller_disable(); esp_bt_controller_deinit(); } static void esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) { switch (event) { case ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT: ESP_LOGI(CONN_TAG, "Advertising data set, status %d", param->adv_data_raw_cmpl.status); // ADV 数据设置完成,等待 Scan Response 也设置完成后再开始广播 break; case ESP_GAP_BLE_SCAN_RSP_DATA_RAW_SET_COMPLETE_EVT: ESP_LOGI(CONN_TAG, "Scan response data set, status %d", param->scan_rsp_data_raw_cmpl.status); esp_ble_gap_start_advertising(&adv_params); break; case ESP_GAP_BLE_ADV_START_COMPLETE_EVT: if (param->adv_start_cmpl.status != ESP_BT_STATUS_SUCCESS) { ESP_LOGE(CONN_TAG, "Advertising start failed, status %d", param->adv_start_cmpl.status); break; } ESP_LOGI(CONN_TAG, "Advertising start successfully"); break; case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT: if (param->adv_stop_cmpl.status != ESP_BT_STATUS_SUCCESS) { ESP_LOGE(CONN_TAG, "Advertising stop failed, status %d", param->adv_stop_cmpl.status); } ESP_LOGI(CONN_TAG, "Advertising stop successfully"); break; case ESP_GAP_BLE_UPDATE_CONN_PARAMS_EVT: ESP_LOGI(CONN_TAG, "Connection params update, status %d, conn_int %d, latency %d, timeout %d", param->update_conn_params.status, param->update_conn_params.conn_int, param->update_conn_params.latency, param->update_conn_params.timeout); break; default: break; } } // GATT服务器事件处理函数 static void gatts_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(CONN_TAG, "GATT server register, status %d, app_id %d",param->reg.status, param->reg.app_id); // 创建图片传输服务 esp_ble_gatts_create_service(gatts_if,&server_id_image,10); break; case ESP_GATTS_CREATE_EVT: if (param->create.status == ESP_GATT_OK) { image_service_handle = param->create.service_handle; esp_ble_gatts_add_char( image_service_handle, &image_write_uuid, ESP_GATT_PERM_WRITE, ESP_GATT_CHAR_PROP_BIT_WRITE | ESP_GATT_CHAR_PROP_BIT_WRITE_NR, &char_val_image_write, &control_image_write ); esp_ble_gatts_add_char( image_service_handle, &image_edit_uuid, ESP_GATT_PERM_WRITE, ESP_GATT_CHAR_PROP_BIT_WRITE | ESP_GATT_CHAR_PROP_BIT_WRITE_NR, &char_val_image_edit, &control_image_edit ); ESP_LOGI(CONN_TAG, "图片传输服务创建成功,句柄: %x", image_service_handle); } else { ESP_LOGE(CONN_TAG, "服务创建失败,状态: %d", param->create.status); } break; case ESP_GATTS_ADD_CHAR_EVT: if (param->add_char.status == ESP_GATT_OK) { if (param->add_char.char_uuid.uuid.uuid16 == (uint16_t)IMAGE_WRITE_UUID) { image_write_handle = param->add_char.attr_handle; ESP_LOGI(CONN_TAG, "图片写入特征创建成功,句柄: %d", image_write_handle); } else if (param->add_char.char_uuid.uuid.uuid16 == (uint16_t)IMAGE_EDIT_UUID) { image_edit_handle = param->add_char.attr_handle; ESP_LOGI(CONN_TAG, "图片编辑特征创建成功,句柄: %d", image_edit_handle); esp_ble_gatts_start_service(image_service_handle); } } else { ESP_LOGE(CONN_TAG, "特征创建失败,状态: %d", param->add_char.status); } break; case ESP_GATTS_WRITE_EVT: if(param->write.handle == image_write_handle){ uint8_t *value = param->write.value; if(!SendStatus.isSend){ ESP_LOGI(CONN_TAG, "处理前序数据"); firstMeg.type = value[0]; memcpy(firstMeg.filename, value + 1, 22); firstMeg.filename[22] = '\0'; firstMeg.len = (value[23] << 16) | (value[24] << 8) | value[25]; ESP_LOGI(CONN_TAG, "图片数据长度:%d",(int)firstMeg.len); if(firstMeg.type == 0xfd){ SendStatus.isSend = true; img_data = malloc((int)firstMeg.len); filepath = malloc(sizeof(char) * 33); sprintf(filepath,"/spiflash/%s",firstMeg.filename); file_img = fopen(filepath,"wb"); ESP_LOGI(CONN_TAG,"传输通道建立成功,数据指针:%p,文件名称:%s,文件大小:%d",img_data,firstMeg.filename,(int)firstMeg.len); } }else if(SendStatus.isSend){ uint8_t pkt_no = *value; uint8_t isEnd = *(value + 1); // 每 100 包或最后一包打印日志(减少串口输出提升传输速度) if (pkt_no % 100 == 0 || isEnd) { ESP_LOGI(CONN_TAG, "获取到数据:第:%d包,长度:%d,是否结束:%d", pkt_no+1, (int)param->write.len, isEnd); } uint8_t *data = value + 2; memcpy(img_data + SendStatus.port,data,(int)param->write.len-2); SendStatus.port += param->write.len-2; if(isEnd){ ESP_LOGI(CONN_TAG,"数据接收完毕,累计:%d字节,预期:%d字节,首字节:%02X %02X", (int)SendStatus.port,(int)firstMeg.len,img_data[0],img_data[1]); fwrite(img_data,sizeof(uint8_t),firstMeg.len,file_img); fclose(file_img); SendStatus.isSend = false; SendStatus.port = 0; // img_data 不释放,传给显示任务直通显示(跳过 SPIFFS 重读) ble_pending_data = img_data; ble_pending_data_size = firstMeg.len; img_data = NULL; // 转移所有权 free(filepath); ESP_LOGI(CONN_TAG,"图片接收成功,数据直通显示(%d字节)", (int)ble_pending_data_size); strncpy(ble_pending_filename, firstMeg.filename, sizeof(ble_pending_filename) - 1); ble_pending_filename[sizeof(ble_pending_filename) - 1] = '\0'; xTaskNotifyGive(ble_process_task_handle); } } }// 图片编辑特征写入事件 else if(param->write.handle == image_edit_handle){ uint8_t *value = param->write.value; char imgName[23]; uint8_t type = *(value + param->write.len - 1); memcpy(imgName, value, 23); if(type == 0xff){ // 耗时操作转移到独立任务执行 strncpy(ble_pending_filename, imgName, sizeof(ble_pending_filename) - 1); ble_pending_filename[sizeof(ble_pending_filename) - 1] = '\0'; xTaskNotifyGive(ble_process_task_handle); }else if(type == 0xF1){ remove(filepath); SendStatus.isSend = false; SendStatus.port = 0; free(img_data); free(filepath); } } break; 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 = 16; // 16 × 1.25ms = 20ms(缩短连接间隔提升传输吞吐量) conn_params.min_int = 6; // 6 × 1.25ms = 7.5ms conn_params.timeout = 400; conn_id = param->connect.conn_id; ESP_LOGI(CONN_TAG, "Connected, conn_id %u, remote "ESP_BD_ADDR_STR"", param->connect.conn_id, ESP_BD_ADDR_HEX(param->connect.remote_bda)); esp_ble_gap_update_conn_params(&conn_params); break; } case ESP_GATTS_DISCONNECT_EVT: ESP_LOGI(CONN_TAG, "Disconnected, remote "ESP_BD_ADDR_STR", reason 0x%02x", ESP_BD_ADDR_HEX(param->disconnect.remote_bda), param->disconnect.reason); esp_ble_gap_start_advertising(&adv_params); break; default: break; } }