#include "ble_service.h" #include "ble_service_config.h" #include #include #include #include #include #include #include #include static const char* TAG = BLE_JSON_TAG; BleJsonService* BleJsonService::instance_ = nullptr; // ============================================================ // 命令队列消息结构 // ============================================================ struct BleJsonCmdMsg { char* json_str; // 动态分配的 JSON 字符串,需要 free uint16_t len; }; // ============================================================ // UUID 定义 // ============================================================ static esp_bt_uuid_t service_uuid = { .len = ESP_UUID_LEN_16, .uuid = {.uuid16 = BLE_JSON_SERVICE_UUID}, }; static esp_bt_uuid_t write_char_uuid = { .len = ESP_UUID_LEN_16, .uuid = {.uuid16 = BLE_JSON_CHAR_WRITE_UUID}, }; static esp_bt_uuid_t notify_char_uuid = { .len = ESP_UUID_LEN_16, .uuid = {.uuid16 = BLE_JSON_CHAR_NOTIFY_UUID}, }; static esp_bt_uuid_t status_char_uuid = { .len = ESP_UUID_LEN_16, .uuid = {.uuid16 = BLE_JSON_CHAR_STATUS_UUID}, }; // CCCD 描述符 UUID (标准 0x2902) static esp_bt_uuid_t cccd_uuid = { .len = ESP_UUID_LEN_16, .uuid = {.uuid16 = ESP_GATT_UUID_CHAR_CLIENT_CONFIG}, }; // ============================================================ // Characteristic 属性值缓冲区 (Auto Response 使用) // ============================================================ static uint8_t write_char_val[BLE_JSON_CHAR_VAL_MAX_LEN] = {0}; static uint8_t notify_char_val[BLE_JSON_CHAR_VAL_MAX_LEN] = {0}; static uint8_t status_char_val[BLE_JSON_CHAR_VAL_MAX_LEN] = {0}; static uint8_t cccd_val[2] = {0x00, 0x00}; static esp_attr_value_t write_char_attr = { .attr_max_len = BLE_JSON_CHAR_VAL_MAX_LEN, .attr_len = 1, .attr_value = write_char_val, }; static esp_attr_value_t notify_char_attr = { .attr_max_len = BLE_JSON_CHAR_VAL_MAX_LEN, .attr_len = 1, .attr_value = notify_char_val, }; static esp_attr_value_t status_char_attr = { .attr_max_len = BLE_JSON_CHAR_VAL_MAX_LEN, .attr_len = 1, .attr_value = status_char_val, }; static esp_attr_value_t cccd_attr = { .attr_max_len = 2, .attr_len = 2, .attr_value = cccd_val, }; // ============================================================ // 广播数据 // ============================================================ static esp_ble_adv_params_t ble_json_adv_params = { .adv_int_min = BLE_JSON_ADV_INT_MIN, .adv_int_max = BLE_JSON_ADV_INT_MAX, .adv_type = ADV_TYPE_IND, .own_addr_type = BLE_ADDR_TYPE_PUBLIC, .peer_addr = {0}, .peer_addr_type = BLE_ADDR_TYPE_PUBLIC, .channel_map = ADV_CHNL_ALL, .adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY, }; // ============================================================ // 构造 / 析构 // ============================================================ BleJsonService::BleJsonService() { instance_ = this; } BleJsonService::~BleJsonService() { Stop(); if (instance_ == this) { instance_ = nullptr; } } // ============================================================ // Initialize — 注册 GATTS App // ============================================================ bool BleJsonService::Initialize() { if (initialized_) { ESP_LOGW(TAG, "Already initialized"); return true; } esp_err_t ret; // 检查 Bluedroid 栈是否已启动 (可能由 BLE 配网模块启动过) // 如果未启动,则自行初始化整个 BLE 栈 esp_bluedroid_status_t bt_status = esp_bluedroid_get_status(); if (bt_status != ESP_BLUEDROID_STATUS_ENABLED) { ESP_LOGI(TAG, "Bluedroid not enabled, initializing BLE stack..."); // 释放经典蓝牙内存 esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT); // 初始化 BT Controller esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT(); ret = esp_bt_controller_init(&bt_cfg); if (ret != ESP_OK && ret != ESP_ERR_INVALID_STATE) { ESP_LOGE(TAG, "BT controller init failed: %s", esp_err_to_name(ret)); return false; } ret = esp_bt_controller_enable(ESP_BT_MODE_BLE); if (ret != ESP_OK && ret != ESP_ERR_INVALID_STATE) { ESP_LOGE(TAG, "BT controller enable failed: %s", esp_err_to_name(ret)); return false; } // 初始化 Bluedroid ret = esp_bluedroid_init(); if (ret != ESP_OK && ret != ESP_ERR_INVALID_STATE) { ESP_LOGE(TAG, "Bluedroid init failed: %s", esp_err_to_name(ret)); return false; } ret = esp_bluedroid_enable(); if (ret != ESP_OK && ret != ESP_ERR_INVALID_STATE) { ESP_LOGE(TAG, "Bluedroid enable failed: %s", esp_err_to_name(ret)); return false; } ESP_LOGI(TAG, "BLE stack initialized successfully"); } // 设置 MTU ret = esp_ble_gatt_set_local_mtu(BLE_JSON_LOCAL_MTU); if (ret != ESP_OK) { ESP_LOGW(TAG, "Set MTU failed: %s (may already be set)", esp_err_to_name(ret)); } // 注册 GAP 回调 (广播事件需要此回调才能启动) // 注意: ESP-IDF 仅支持一个全局 GAP 回调,此处会覆盖配网模块的回调 // 但 BLE 配网流程在 StartNetwork() 中已完成,不影响后续使用 ret = esp_ble_gap_register_callback(GapEventHandler); if (ret != ESP_OK) { ESP_LOGW(TAG, "GAP callback register: %s", esp_err_to_name(ret)); } // 注册 GATTS 回调 (全局只能有一个,但 Bluedroid 支持按 gatts_if 分发) // 配网模块已经注册了自己的回调,我们通过 app_register 获得独立的 gatts_if ret = esp_ble_gatts_register_callback(GattsEventHandler); if (ret != ESP_OK) { ESP_LOGW(TAG, "GATTS callback register: %s (may share with provisioning)", esp_err_to_name(ret)); } // 注册独立的 GATTS App,获得自己的 gatts_if ret = esp_ble_gatts_app_register(BLE_JSON_APP_ID); if (ret != ESP_OK) { ESP_LOGE(TAG, "GATTS app register failed: %s", esp_err_to_name(ret)); return false; } // 创建命令处理队列和任务 cmd_queue_ = xQueueCreate(BLE_JSON_CMD_QUEUE_SIZE, sizeof(BleJsonCmdMsg)); if (!cmd_queue_) { ESP_LOGE(TAG, "Failed to create cmd queue"); return false; } xTaskCreate(CmdProcessTask, "ble_json_cmd", 6144, this, 5, &cmd_task_handle_); initialized_ = true; ESP_LOGI(TAG, "Initialized successfully (App ID=%d)", BLE_JSON_APP_ID); return true; } // ============================================================ // Start — 启动广播 // ============================================================ bool BleJsonService::Start(const char* device_name) { if (!initialized_) { ESP_LOGE(TAG, "Not initialized"); return false; } device_name_ = device_name ? device_name : BLE_JSON_DEVICE_NAME; // 设置设备名称 esp_ble_gap_set_device_name(device_name_.c_str()); StartAdvertising(); ESP_LOGI(TAG, "Started, device name: %s", device_name_.c_str()); return true; } // ============================================================ // Stop // ============================================================ void BleJsonService::Stop() { if (!initialized_) return; esp_ble_gap_stop_advertising(); if (cmd_task_handle_) { vTaskDelete(cmd_task_handle_); cmd_task_handle_ = nullptr; } if (cmd_queue_) { // 清空队列中残留的消息 BleJsonCmdMsg msg; while (xQueueReceive(cmd_queue_, &msg, 0) == pdTRUE) { free(msg.json_str); } vQueueDelete(cmd_queue_); cmd_queue_ = nullptr; } connected_ = false; notify_enabled_ = false; initialized_ = false; ESP_LOGI(TAG, "Stopped"); } // ============================================================ // SendResponse — 构建 JSON 响应并通过 NOTIFY 发送 // ============================================================ bool BleJsonService::SendResponse(const std::string& cmd, int msg_id, int code, const char* msg, cJSON* data) { if (!connected_ || !notify_enabled_) { ESP_LOGW(TAG, "Cannot send: connected=%d notify=%d", connected_, notify_enabled_); return false; } cJSON* root = cJSON_CreateObject(); cJSON_AddStringToObject(root, "cmd", cmd.c_str()); cJSON_AddNumberToObject(root, "id", msg_id); cJSON_AddNumberToObject(root, "code", code); if (msg) { cJSON_AddStringToObject(root, "msg", msg); } if (data) { cJSON_AddItemReferenceToObject(root, "data", data); } char* json_str = cJSON_PrintUnformatted(root); cJSON_Delete(root); if (!json_str) { ESP_LOGE(TAG, "JSON print failed"); return false; } uint16_t len = strlen(json_str); ESP_LOGI(TAG, "TX(%d): %s", len, json_str); bool ok = SendNotify(json_str, len); free(json_str); return ok; } // ============================================================ // SendEvent — 构建主动推送事件 // ============================================================ bool BleJsonService::SendEvent(const std::string& event_type, cJSON* data) { if (!connected_ || !notify_enabled_) { return false; } cJSON* root = cJSON_CreateObject(); cJSON_AddStringToObject(root, "cmd", "event"); cJSON* event_data = data ? cJSON_Duplicate(data, true) : cJSON_CreateObject(); cJSON_AddStringToObject(event_data, "type", event_type.c_str()); cJSON_AddItemToObject(root, "data", event_data); char* json_str = cJSON_PrintUnformatted(root); cJSON_Delete(root); if (!json_str) return false; uint16_t len = strlen(json_str); ESP_LOGI(TAG, "TX event(%d): %s", len, json_str); bool ok = SendNotify(json_str, len); free(json_str); return ok; } // ============================================================ // SendNotify — 底层 NOTIFY 发送 // ============================================================ bool BleJsonService::SendNotify(const char* json_str, uint16_t len) { if (gatts_if_ == ESP_GATT_IF_NONE || !connected_) { return false; } // 检查是否超过 MTU uint16_t max_payload = mtu_ - 3; if (len > max_payload) { ESP_LOGW(TAG, "Data len %d exceeds MTU payload %d, truncating", len, max_payload); len = max_payload; } esp_err_t ret = esp_ble_gatts_send_indicate( gatts_if_, conn_id_, notify_char_handle_, len, (uint8_t*)json_str, false // false = notification, true = indication ); if (ret != ESP_OK) { ESP_LOGE(TAG, "Send notify failed: %s", esp_err_to_name(ret)); return false; } return true; } // ============================================================ // StartAdvertising // ============================================================ void BleJsonService::StartAdvertising() { // 广播包:不放名称,避免超过31字节导致手机系统蓝牙搜索不到 esp_ble_adv_data_t adv_data = {}; adv_data.set_scan_rsp = false; adv_data.include_name = false; // 名称放在 Scan Response 中 adv_data.include_txpower = true; adv_data.min_interval = 0x0006; adv_data.max_interval = 0x0010; adv_data.appearance = 0x00; adv_data.manufacturer_len = 0; adv_data.p_manufacturer_data = nullptr; adv_data.service_data_len = 0; adv_data.p_service_data = nullptr; adv_data.service_uuid_len = sizeof(uint16_t); adv_data.p_service_uuid = (uint8_t*)&service_uuid.uuid.uuid16; adv_data.flag = (ESP_BLE_ADV_FLAG_GEN_DISC | ESP_BLE_ADV_FLAG_BREDR_NOT_SPT); esp_ble_gap_config_adv_data(&adv_data); // Scan Response:放完整设备名称,手机扫描时会主动请求 esp_ble_adv_data_t scan_rsp = {}; scan_rsp.set_scan_rsp = true; scan_rsp.include_name = true; scan_rsp.include_txpower = false; esp_ble_gap_config_adv_data(&scan_rsp); } // ============================================================ // GAP 事件回调 // ============================================================ void BleJsonService::GapEventHandler(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: case ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT: { // 两个事件都到达后才启动广播 static uint8_t adv_config_done = 0; if (event == ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT) { adv_config_done |= 0x01; } else { adv_config_done |= 0x02; } if (adv_config_done == 0x03) { adv_config_done = 0; // 重置,供下次重新广播使用 esp_ble_gap_start_advertising(&ble_json_adv_params); } break; } case ESP_GAP_BLE_ADV_START_COMPLETE_EVT: if (param->adv_start_cmpl.status != ESP_BT_STATUS_SUCCESS) { ESP_LOGE(TAG, "Advertising start failed: %d", param->adv_start_cmpl.status); } else { ESP_LOGI(TAG, "Advertising started"); } break; case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT: ESP_LOGI(TAG, "Advertising stopped"); break; default: break; } } // ============================================================ // GATTS 事件回调 (static 分发) // ============================================================ void BleJsonService::GattsEventHandler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t* param) { // 只处理属于本 App 的事件 (通过 gatts_if 或 REG 事件匹配) if (event == ESP_GATTS_REG_EVT) { if (param->reg.app_id != BLE_JSON_APP_ID) { return; // 不是我们的 App 注册事件 } } else { // 非 REG 事件: 检查 gatts_if 是否属于我们 if (!instance_ || gatts_if != instance_->gatts_if_) { return; } } if (instance_) { instance_->HandleGattsEvent(event, gatts_if, param); } } // ============================================================ // HandleGattsEvent — 实例内处理 // ============================================================ void BleJsonService::HandleGattsEvent(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t* param) { switch (event) { // ---- App 注册完成,保存 gatts_if,创建 Service ---- case ESP_GATTS_REG_EVT: if (param->reg.status == ESP_GATT_OK) { gatts_if_ = gatts_if; ESP_LOGI(TAG, "GATTS app registered, gatts_if=%d", gatts_if); CreateService(gatts_if); } else { ESP_LOGE(TAG, "GATTS app register failed, status=%d", param->reg.status); } break; // ---- Service 创建完成,开始添加 Characteristic ---- case ESP_GATTS_CREATE_EVT: if (param->create.status == ESP_GATT_OK) { service_handle_ = param->create.service_handle; ESP_LOGI(TAG, "Service created, handle=%d", service_handle_); chars_added_ = 0; // 1. 添加 WRITE Characteristic (App -> 设备) esp_gatt_char_prop_t write_prop = ESP_GATT_CHAR_PROP_BIT_WRITE; esp_ble_gatts_add_char(service_handle_, &write_char_uuid, ESP_GATT_PERM_WRITE, write_prop, &write_char_attr, nullptr); } else { ESP_LOGE(TAG, "Service create failed: %d", param->create.status); } break; // ---- Characteristic 添加完成 ---- case ESP_GATTS_ADD_CHAR_EVT: if (param->add_char.status != ESP_GATT_OK) { ESP_LOGE(TAG, "Add char failed: uuid=0x%04x status=%d", param->add_char.char_uuid.uuid.uuid16, param->add_char.status); break; } if (param->add_char.char_uuid.uuid.uuid16 == BLE_JSON_CHAR_WRITE_UUID) { write_char_handle_ = param->add_char.attr_handle; ESP_LOGI(TAG, "WRITE char added, handle=%d", write_char_handle_); // 2. 添加 NOTIFY Characteristic (设备 -> App) esp_gatt_char_prop_t notify_prop = ESP_GATT_CHAR_PROP_BIT_NOTIFY | ESP_GATT_CHAR_PROP_BIT_READ; esp_ble_gatts_add_char(service_handle_, ¬ify_char_uuid, ESP_GATT_PERM_READ, notify_prop, ¬ify_char_attr, nullptr); } else if (param->add_char.char_uuid.uuid.uuid16 == BLE_JSON_CHAR_NOTIFY_UUID) { notify_char_handle_ = param->add_char.attr_handle; ESP_LOGI(TAG, "NOTIFY char added, handle=%d", notify_char_handle_); // 为 NOTIFY char 添加 CCCD 描述符 esp_ble_gatts_add_char_descr(service_handle_, &cccd_uuid, ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE, &cccd_attr, nullptr); } else if (param->add_char.char_uuid.uuid.uuid16 == BLE_JSON_CHAR_STATUS_UUID) { status_char_handle_ = param->add_char.attr_handle; ESP_LOGI(TAG, "STATUS char added, handle=%d", status_char_handle_); // 所有 Characteristic 添加完毕,启动 Service esp_ble_gatts_start_service(service_handle_); } break; // ---- CCCD 描述符添加完成 ---- case ESP_GATTS_ADD_CHAR_DESCR_EVT: if (param->add_char_descr.status == ESP_GATT_OK) { notify_cccd_handle_ = param->add_char_descr.attr_handle; ESP_LOGI(TAG, "CCCD added, handle=%d", notify_cccd_handle_); // 3. 添加 STATUS Characteristic (READ only) esp_gatt_char_prop_t status_prop = ESP_GATT_CHAR_PROP_BIT_READ; esp_ble_gatts_add_char(service_handle_, &status_char_uuid, ESP_GATT_PERM_READ, status_prop, &status_char_attr, nullptr); } else { ESP_LOGE(TAG, "Add CCCD failed: %d", param->add_char_descr.status); } break; // ---- Service 启动完成 ---- case ESP_GATTS_START_EVT: if (param->start.status == ESP_GATT_OK) { ESP_LOGI(TAG, "Service started"); } break; // ---- 客户端连接 ---- case ESP_GATTS_CONNECT_EVT: { conn_id_ = param->connect.conn_id; connected_ = true; notify_enabled_ = false; mtu_ = 23; // 默认值,等待 MTU exchange ESP_LOGI(TAG, "Client connected, conn_id=%d, addr=%02x:%02x:%02x:%02x:%02x:%02x", 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]); // 请求更新连接参数 esp_ble_conn_update_params_t conn_params = {}; memcpy(conn_params.bda, param->connect.remote_bda, sizeof(esp_bd_addr_t)); conn_params.latency = 0; conn_params.max_int = 0x20; // 40ms conn_params.min_int = 0x10; // 20ms conn_params.timeout = 400; // 4s esp_ble_gap_update_conn_params(&conn_params); // 连接后停止广播 (BLE 4.2 单连接时自动停止,但显式调用更安全) esp_ble_gap_stop_advertising(); break; } // ---- 客户端断开 ---- case ESP_GATTS_DISCONNECT_EVT: ESP_LOGI(TAG, "Client disconnected, reason=0x%x", param->disconnect.reason); connected_ = false; notify_enabled_ = false; mtu_ = 23; // 重新启动广播 StartAdvertising(); break; // ---- MTU 协商完成 ---- case ESP_GATTS_MTU_EVT: mtu_ = param->mtu.mtu; ESP_LOGI(TAG, "MTU updated: %d", mtu_); break; // ---- WRITE 事件 ---- case ESP_GATTS_WRITE_EVT: if (param->write.handle == write_char_handle_) { // JSON 命令数据 ProcessWriteData(param->write.value, param->write.len); } else if (param->write.handle == notify_cccd_handle_ && param->write.len == 2) { // CCCD 写入: 开启/关闭 NOTIFY uint16_t cccd_value = param->write.value[0] | (param->write.value[1] << 8); notify_enabled_ = (cccd_value == 0x0001); ESP_LOGI(TAG, "NOTIFY %s", notify_enabled_ ? "enabled" : "disabled"); } // 如果需要响应 if (param->write.need_rsp) { esp_ble_gatts_send_response(gatts_if_, param->write.conn_id, param->write.trans_id, ESP_GATT_OK, nullptr); } break; // ---- READ 事件 (STATUS char) ---- case ESP_GATTS_READ_EVT: // Auto response 模式下无需手动处理 // 如果需要动态数据,可在此更新 status_char_val ESP_LOGD(TAG, "Read event, handle=%d", param->read.handle); break; default: break; } } // ============================================================ // CreateService // ============================================================ void BleJsonService::CreateService(esp_gatt_if_t gatts_if) { esp_gatt_srvc_id_t service_id = {}; service_id.is_primary = true; service_id.id.inst_id = 0; service_id.id.uuid.len = ESP_UUID_LEN_16; service_id.id.uuid.uuid.uuid16 = BLE_JSON_SERVICE_UUID; esp_ble_gatts_create_service(gatts_if, &service_id, BLE_JSON_HANDLE_NUM); } // ============================================================ // ProcessWriteData — 收到 WRITE 数据,放入队列 // ============================================================ void BleJsonService::ProcessWriteData(const uint8_t* data, uint16_t len) { if (!data || len == 0 || !cmd_queue_) return; // 拷贝数据到堆上 (确保 null 结尾) char* json_str = (char*)malloc(len + 1); if (!json_str) { ESP_LOGE(TAG, "Malloc failed for cmd data"); return; } memcpy(json_str, data, len); json_str[len] = '\0'; ESP_LOGI(TAG, "RX(%d): %s", len, json_str); BleJsonCmdMsg msg = {json_str, len}; if (xQueueSend(cmd_queue_, &msg, pdMS_TO_TICKS(100)) != pdTRUE) { ESP_LOGW(TAG, "Cmd queue full, dropping"); free(json_str); } } // ============================================================ // CmdProcessTask — 从队列取出 JSON 并分发到回调 // ============================================================ void BleJsonService::CmdProcessTask(void* param) { BleJsonService* self = static_cast(param); BleJsonCmdMsg msg; while (true) { if (xQueueReceive(self->cmd_queue_, &msg, portMAX_DELAY) == pdTRUE) { // 解析 JSON cJSON* root = cJSON_Parse(msg.json_str); if (!root) { ESP_LOGW(TAG, "JSON parse failed: %s", msg.json_str); // 发送错误响应 if (self->connected_ && self->notify_enabled_) { self->SendResponse("error", 0, 1, "invalid json"); } free(msg.json_str); continue; } // 提取 cmd 和 id cJSON* cmd_item = cJSON_GetObjectItem(root, "cmd"); cJSON* id_item = cJSON_GetObjectItem(root, "id"); cJSON* data_item = cJSON_GetObjectItem(root, "data"); if (!cmd_item || !cJSON_IsString(cmd_item)) { ESP_LOGW(TAG, "Missing 'cmd' field"); self->SendResponse("error", 0, 1, "missing cmd"); cJSON_Delete(root); free(msg.json_str); continue; } std::string cmd = cmd_item->valuestring; int msg_id = (id_item && cJSON_IsNumber(id_item)) ? id_item->valueint : 0; // 分发到用户回调 if (self->command_callback_) { self->command_callback_(cmd, msg_id, data_item); } else { ESP_LOGW(TAG, "No command callback, cmd=%s ignored", cmd.c_str()); self->SendResponse(cmd, msg_id, 2, "no handler"); } cJSON_Delete(root); free(msg.json_str); } } }