/** * @file fc41d_ble.c * @brief FC41D 蓝牙调试完整实现:AT 收发、模组电源、BLE 状态机、DIP 选路、comm_link 后端 * * 模块内部分为四段(均为 static 或本文件内可见): * 1. AT 命令层 — UART7 与 FC41D 对话,解析 +QBLESTAT URC * 2. 硬件控制 — PE6(FC41D_RST) 复位脉冲与释放,非电源使能 * 3. BLE 状态机 — 外设初始化、等待连接、QBLETRANMODE 透传数据交互 * 4. 对外接口 — ble_link_* / comm_link_ble_* 供 tcp_ser_task、comm_link 调用 */ #include "publicdata/public_define.h" #if BLE_DEBUG_EN #include "fc41d_ble.h" #include "fc41d_ble_priv.h" #include "debug_link.h" #if TCP_DEBUG_EN #include "debug_files.h" #include "tcp_protocol.h" #endif #include "gpio.h" #include "usart.h" #include "sys_drv_init.h" #include "publicdata/publicdata.h" #include "app_init/app_init.h" #include "sub_comm/sub_comm_task.h" #include "wdt_task/wdt_task.h" #include "FreeRTOS.h" #include "gd32h7xx.h" #include #include /* UART7(FC41D AT)专用:不扩展公共 usart 驱动,逻辑限定在本文件 */ static void s_fc41d_uart_init(void) { rcu_periph_clock_enable(RCU_GPIOE); rcu_periph_clock_enable(RCU_UART7); gpio_af_set(GPIOE, GPIO_AF_8, GPIO_PIN_1); gpio_mode_set(GPIOE, GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO_PIN_1); gpio_output_options_set(GPIOE, GPIO_OTYPE_PP, GPIO_OSPEED_60MHZ, GPIO_PIN_1); gpio_af_set(GPIOE, GPIO_AF_8, GPIO_PIN_0); gpio_mode_set(GPIOE, GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO_PIN_0); gpio_output_options_set(GPIOE, GPIO_OTYPE_PP, GPIO_OSPEED_60MHZ, GPIO_PIN_0); usart_deinit(UART7); usart_word_length_set(UART7, USART_WL_8BIT); usart_stop_bit_set(UART7, USART_STB_1BIT); usart_parity_config(UART7, USART_PM_NONE); usart_baudrate_set(UART7, FC41D_UART_BAUD); usart_hardware_flow_rts_config(UART7, USART_RTS_DISABLE); usart_hardware_flow_cts_config(UART7, USART_CTS_DISABLE); usart_receive_config(UART7, USART_RECEIVE_ENABLE); usart_transmit_config(UART7, USART_TRANSMIT_ENABLE); usart_interrupt_enable(UART7, USART_INT_RBNE); usart_enable(UART7); } static void s_fc41d_uart_discard(void) { uint8_t scrap[64]; while (SET == usart_flag_get(UART7, USART_FLAG_RBNE)) { (void)usart_data_receive(UART7); } for (;;) { int n = v_usart_read_rx_buffer(FC41D_UART_HW_NUM, scrap, (uint16_t)sizeof(scrap)); if (n <= 0) { break; } } } static uint16_t s_fc41d_uart_drain_rx(uint8_t *buf, uint16_t cap) { uint16_t total = 0U; if (buf == NULL || cap == 0U) { return 0U; } while (total < cap && (SET == usart_flag_get(UART7, USART_FLAG_RBNE))) { buf[total++] = (uint8_t)usart_data_receive(UART7); } { int got = v_usart_read_rx_buffer(FC41D_UART_HW_NUM, &buf[total], (uint16_t)(cap - total)); if (got > 0) { total += (uint16_t)got; } } return total; } #define FC41D_LINK_CAP() (BLE->io.link_rx_acc_cap) #define FC41D_APP_CAP() (BLE->io.app_rx_cap) /* ============================================================================= * 模块常量与上下文宏(状态封装在 comm_link_ctx.backend.ble / session) * ============================================================================= */ /** 单次 QBLEGATTSNTFY 最大载荷(字节) */ #define FC41D_GATT_TX_CHUNK 400U #define SESS (comm_link_session()) #define BLE (&(comm_link_ctx_get()->backend.ble)) /* ============================================================================= * AT 命令层 — 静态函数 * ============================================================================= */ static void v_fc41d_parse_qble_recv(uint8_t *buf, uint16_t *p_len); static void v_at_uart_read_all(void); static void v_at_uart_discard(void); static void v_at_uart_drain_payload(void); static int s_fc41d_resp_has_ok(const uint8_t *buf, uint16_t len); static int s_fc41d_link_or_at_has_ok(void); static void s_ble_drain_uart_before_gatt(void); #if TCP_DEBUG_EN static void s_ble_log_json_io(const char *dir, const uint8_t *data, uint32_t len); #endif static void v_at_set_transparent(uint8_t on); static uint8_t u8_at_is_transparent(void); static int s_ble_enter_transparent_blocking(void); static int s_ble_exit_transparent_blocking(void); /** * @brief 将 UART 读到的原始字节追加到 BLE->at.rx_acc * @param data 数据指针 * @param len 字节数;为 0 或 data 为 NULL 时不操作 * @note 缓冲满时从头覆盖,避免长时间运行撑爆内存 */ static void v_at_append_rx(const uint8_t *data, uint16_t len) { uint16_t i; if ((data == NULL) || (len == 0U)) { return; } for (i = 0U; i < len; i++) { if (BLE->at.rx_acc_len >= (FC41D_AT_RX_ACC_SIZE - 1U)) { BLE->at.rx_acc_len = 0U; } BLE->at.rx_acc[BLE->at.rx_acc_len++] = (char)data[i]; BLE->at.rx_acc[BLE->at.rx_acc_len] = '\0'; } } static void v_fc41d_shift_rx(uint8_t *buf, uint16_t *p_len, uint16_t consume) { uint16_t len = *p_len; if (consume >= len) { *p_len = 0U; return; } memmove(buf, &buf[consume], (size_t)(len - consume)); *p_len = (uint16_t)(len - consume); } static void s_app_rx_clear(void) { BLE->io.app_rx_wr = 0U; BLE->io.app_rx_rd = 0U; BLE->io.qble_payload_need = 0U; BLE->io.link_rx_acc_len = 0U; } static void s_ble_io_free(void) { if (BLE->io.link_rx_acc != NULL) { vPortFree(BLE->io.link_rx_acc); BLE->io.link_rx_acc = NULL; } if (BLE->io.app_rx != NULL) { vPortFree(BLE->io.app_rx); BLE->io.app_rx = NULL; } BLE->io.link_rx_acc_cap = 0U; BLE->io.app_rx_cap = 0U; s_app_rx_clear(); } static int s_ble_io_set_caps(uint16_t link_cap, uint16_t app_cap) { if ((link_cap == 0U) || (app_cap == 0U)) { return -1; } if ((BLE->io.link_rx_acc != NULL) && (BLE->io.link_rx_acc_cap == link_cap) && (BLE->io.app_rx != NULL) && (BLE->io.app_rx_cap == app_cap)) { s_app_rx_clear(); return 0; } s_ble_io_free(); BLE->io.link_rx_acc = (uint8_t *)pvPortMalloc((size_t)link_cap); BLE->io.app_rx = (uint8_t *)pvPortMalloc((size_t)app_cap); if ((BLE->io.link_rx_acc == NULL) || (BLE->io.app_rx == NULL)) { s_ble_io_free(); BT_PRINT("BLE RX buf alloc fail link=%u app=%u\r\n", (unsigned)link_cap, (unsigned)app_cap); return -1; } BLE->io.link_rx_acc_cap = link_cap; BLE->io.app_rx_cap = app_cap; s_app_rx_clear(); return 0; } #if TCP_DEBUG_EN static int s_ble_diag_trace(void) { return (system_filetransfer_is_active() != 0) ? 1 : 0; } int comm_link_ble_diag_trace(void) { return s_ble_diag_trace(); } static void s_ble_log_json_io(const char *dir, const uint8_t *data, uint32_t len) { uint32_t off = 0U; enum { kChunk = 256U }; if ((dir == NULL) || (data == NULL) || (len == 0U)) { return; } printf("[BLE-JSON] %s (%u): ", dir, (unsigned)len); if (len <= kChunk) { printf("%.*s\r\n", (int)len, (const char *)data); return; } while (off < len) { uint32_t n = len - off; if (n > kChunk) { n = kChunk; } printf("%.*s", (int)n, (const char *)(data + off)); off += n; } printf("\r\n"); } static uint16_t s_app_rx_pending(void) { uint16_t cap = FC41D_APP_CAP(); uint16_t rd = BLE->io.app_rx_rd; uint16_t wr = BLE->io.app_rx_wr; if (cap == 0U) { return 0U; } if (wr >= rd) { return (uint16_t)(wr - rd); } return (uint16_t)(cap - rd + wr); } static void s_fw_log_link_cache(const char *when) { uint16_t len; uint16_t app_pending; if (s_ble_diag_trace() == 0) { return; } if (BLE->io.link_rx_acc == NULL) { return; } len = BLE->io.link_rx_acc_len; app_pending = s_app_rx_pending(); if ((len == 0U) && (app_pending == 0U) && (BLE->io.qble_payload_need == 0U)) { return; } printf("[FW_UPGRADE] LINK-CACHE@%s link_len=%u need=%u link_cap=%u app_pending=%u\r\n", when, (unsigned)len, (unsigned)BLE->io.qble_payload_need, (unsigned)FC41D_LINK_CAP(), (unsigned)app_pending); if (len > 0U) { tcp_firmware_upgrade_log_buf("LINK-CACHE-DATA", BLE->io.link_rx_acc, (uint32_t)len); } } #endif static int s_app_rx_push(const uint8_t *data, uint16_t len) { uint16_t i; uint16_t cap = FC41D_APP_CAP(); if ((data == NULL) || (len == 0U) || (BLE->io.app_rx == NULL) || (cap == 0U)) { return 0; } for (i = 0U; i < len; i++) { uint16_t next = (uint16_t)((BLE->io.app_rx_wr + 1U) % cap); if (next == BLE->io.app_rx_rd) { BT_PRINT("app_rx overflow, lost %u bytes\r\n", (unsigned)(len - i)); break; } BLE->io.app_rx[BLE->io.app_rx_wr] = data[i]; BLE->io.app_rx_wr = next; } #if TCP_DEBUG_EN if ((s_ble_diag_trace() != 0) && (i > 0U)) { tcp_firmware_upgrade_log_buf("APP-PUSH", data, (uint32_t)i); s_fw_log_link_cache("after-app-push"); } #endif return (int)i; } static int s_app_rx_pop(uint8_t *buf, uint32_t cap_out) { uint32_t n = 0U; uint16_t cap = FC41D_APP_CAP(); if ((buf == NULL) || (cap_out == 0U) || (BLE->io.app_rx == NULL) || (cap == 0U)) { return 0; } while ((n < cap_out) && (BLE->io.app_rx_rd != BLE->io.app_rx_wr)) { buf[n++] = BLE->io.app_rx[BLE->io.app_rx_rd]; BLE->io.app_rx_rd = (uint16_t)((BLE->io.app_rx_rd + 1U) % cap); } return (int)n; } static void v_at_set_transparent(uint8_t on) { BLE->sm.transparent = (on != 0U) ? 1U : 0U; if (on != 0U) { v_at_uart_discard(); s_app_rx_clear(); BT_PRINT("Enter QBLETRANMODE uart passthrough\r\n"); } else { BT_PRINT("Leave uart passthrough\r\n"); } } static uint8_t u8_at_is_transparent(void) { return BLE->sm.transparent; } /** 透传模式:UART 载荷直接写入 app_rx 环 */ static void v_at_uart_drain_payload(void) { uint8_t chunk[128]; uint16_t n; if (u8_at_is_transparent() == 0U) { return; } do { n = s_fc41d_uart_drain_rx(chunk, sizeof(chunk)); if (n > 0U) { #if TCP_DEBUG_EN if (s_ble_diag_trace() != 0) { tcp_firmware_upgrade_log_buf("UART-RX", chunk, (uint32_t)n); } #endif (void)s_app_rx_push(chunk, n); } } while (n == (uint16_t)sizeof(chunk)); } /** * @brief 排空 UART 到 link 解析器(保留 +QBLERECV 应用数据,不清硬件 FIFO 中业务载荷) */ static void v_at_uart_drain_link(void) { uint8_t round; for (round = 0U; round < 4U; round++) { v_at_uart_read_all(); } } /** * @brief 在 link 缓冲内对齐到下一帧 +QBLERECV(仅帧头对齐,禁止 strstr 全缓冲搜索) */ static void v_fc41d_qble_resync(uint8_t *buf, uint16_t *p_len) { uint16_t len = *p_len; uint16_t i; if (len < 10U) { return; } if (strncmp((char *)buf, "+QBLERECV:", 10) == 0) { return; } for (i = 1U; i + 10U <= len; i++) { if (strncmp((char *)&buf[i], "+QBLERECV:", 10) == 0) { v_fc41d_shift_rx(buf, p_len, i); return; } } if (len > (FC41D_LINK_CAP() / 2U)) { v_fc41d_shift_rx(buf, p_len, (uint16_t)(len - 64U)); } } /** * @brief 在缓冲区内查找 +QBLERECV 帧头(续接载荷时避免吞掉下一帧) * @return 帧头偏移,未找到 -1 */ static int s_fc41d_qble_header_at(const uint8_t *buf, uint16_t len) { uint16_t i; if ((buf == NULL) || (len < 10U)) { return -1; } for (i = 0U; i + 10U <= len; i++) { if (strncmp((char *)&buf[i], "+QBLERECV:", 10) == 0) { return (int)i; } } return -1; } /** @return 1 缓冲开头为 +QBLERECV: 的前缀(UART 分片头行) */ static int s_fc41d_is_qble_header_prefix(const uint8_t *buf, uint16_t len) { static const char k_hdr[] = "+QBLERECV:"; uint16_t i; if ((buf == NULL) || (len == 0U)) { return 0; } for (i = 0U; i < len; i++) { if (i >= (sizeof(k_hdr) - 1U)) { return 0; } if (buf[i] != (uint8_t)k_hdr[i]) { return 0; } } return 1; } /** @return 1 缓冲区内疑似 +QBLERECV AT 头行(含分片,禁止写入 app_rx) */ static int s_fc41d_looks_like_qble_at_text(const uint8_t *buf, uint16_t len) { uint16_t scan; if ((buf == NULL) || (len == 0U)) { return 0; } if (s_fc41d_is_qble_header_prefix(buf, len)) { return 1; } if (len >= 7U && memcmp(buf, "BLERECV", 7) == 0) { return 1; } if (len >= 6U && memcmp(buf, "LERECV", 6) == 0) { return 1; } if (len >= 5U && memcmp(buf, "RECV:", 5) == 0) { return 1; } if (len >= 4U && memcmp(buf, "ECV:", 4) == 0) { return 1; } if (len >= 2U && buf[0] == (uint8_t)'V' && buf[1] == (uint8_t)':') { return 1; } for (scan = 0U; (scan + 7U) <= len && scan < 64U; scan++) { if (memcmp(&buf[scan], "BLERECV", 7) == 0) { return 1; } if ((scan + 5U) <= len && memcmp(&buf[scan], "FFF2,", 5) == 0) { return 1; } } return 0; } /** @return 1 缓冲含 AT 头行 CRLF(GATT JSON 载荷内不应出现裸 CRLF) */ static int s_fc41d_buf_has_at_crlf(const uint8_t *buf, uint16_t len) { uint16_t i; if ((buf == NULL) || (len < 2U)) { return 0; } for (i = 0U; i + 1U < len && i < 80U; i++) { if (buf[i] == (uint8_t)'\r' && buf[i + 1U] == (uint8_t)'\n') { return 1; } } return 0; } /** @return 1 无 +QBLERECV 包裹的 JSON/Base64 续接块(UART 拆包遗留) */ static int s_fc41d_is_orphan_app_payload(const uint8_t *buf, uint16_t len) { uint8_t c; if ((buf == NULL) || (len == 0U)) { return 0; } c = buf[0]; if (c == (uint8_t)'{') { return 1; } if ((c >= (uint8_t)'A' && c <= (uint8_t)'Z') || (c >= (uint8_t)'a' && c <= (uint8_t)'z') || (c >= (uint8_t)'0' && c <= (uint8_t)'9') || (c == (uint8_t)'=') || (c == (uint8_t)'+') || (c == (uint8_t)'/')) { return (s_fc41d_looks_like_qble_at_text(buf, len) == 0) ? 1 : 0; } return 0; } static void v_fc41d_drain_orphan_app_payload(uint8_t *buf, uint16_t *p_len) { uint16_t len = *p_len; if (s_ble_diag_trace() == 0) { return; } if ((len == 0U) || (BLE->io.qble_payload_need > 0U)) { return; } if (buf[0] == (uint8_t)'+') { return; } if (s_fc41d_looks_like_qble_at_text(buf, len)) { return; } if (s_fc41d_qble_header_at(buf, len) >= 0) { return; } if (s_fc41d_is_orphan_app_payload(buf, len) == 0) { return; } (void)s_app_rx_push(buf, len); v_fc41d_shift_rx(buf, p_len, len); } /** * @brief 续接上一帧未收齐的 +QBLERECV 二进制载荷 */ static void v_fc41d_qble_payload_continue(uint8_t *buf, uint16_t *p_len) { uint16_t len = *p_len; uint16_t take; int hdr; if (BLE->io.qble_payload_need == 0U || len == 0U) { return; } hdr = s_fc41d_qble_header_at(buf, len); if (hdr == 0) { /* 下一帧 +QBLERECV 已到(多次 Write 或 UART 对齐),放弃残缺续接 */ BLE->io.qble_payload_need = 0U; return; } if (hdr < 0) { /* UART 分片到达 +QBLERECV 头行时勿当作 GATT 载荷 */ if (s_fc41d_looks_like_qble_at_text(buf, len) || s_fc41d_buf_has_at_crlf(buf, len)) { BLE->io.qble_payload_need = 0U; return; } } take = len; if (hdr > 0) { take = (uint16_t)hdr; } if (take > BLE->io.qble_payload_need) { take = BLE->io.qble_payload_need; } if (take == 0U) { return; } (void)s_app_rx_push(buf, take); BLE->io.qble_payload_need = (uint16_t)(BLE->io.qble_payload_need - take); v_fc41d_shift_rx(buf, p_len, take); } /** * @brief 解析缓冲开头的 +QBLERECV 帧(头行 + 定长载荷);禁止在 Base64 载荷中误匹配 * @return 1 消费了一帧或坏头行,0 等待更多数据 */ static int v_fc41d_try_parse_qble_recv_at_start(uint8_t *buf, uint16_t *p_len) { char *base = (char *)buf; char *eol = NULL; int role = 0; int data_len = 0; char uuid[12] = {0}; uint16_t len = *p_len; uint16_t payload_off; uint16_t payload_in_buf; if (len < 12U || strncmp(base, "+QBLERECV:", 10) != 0) { return 0; } for (uint16_t i = 10U; i + 1U < len; i++) { if (base[i] == '\r' && base[i + 1U] == '\n') { eol = &base[i]; break; } } if (eol == NULL) { return 0; } if (sscanf(base, "+QBLERECV:%d,%11[^,],%d", &role, uuid, &data_len) < 3 || data_len <= 0) { v_fc41d_shift_rx(buf, p_len, (uint16_t)((eol + 2) - base)); return 1; } payload_off = (uint16_t)((eol + 2) - base); payload_in_buf = (len > payload_off) ? (uint16_t)(len - payload_off) : 0U; if (payload_in_buf >= (uint16_t)data_len) { (void)s_app_rx_push((const uint8_t *)&buf[payload_off], (uint16_t)data_len); v_fc41d_shift_rx(buf, p_len, (uint16_t)(payload_off + (uint16_t)data_len)); BLE->io.qble_payload_need = 0U; #if TCP_DEBUG_EN if (s_ble_diag_trace() != 0) { printf("[FW_UPGRADE] QBLE full uuid=%s data_len=%d pushed=%d\r\n", uuid, data_len, data_len); } #endif } else { if (payload_in_buf > 0U) { (void)s_app_rx_push((const uint8_t *)&buf[payload_off], payload_in_buf); } if ((payload_off + payload_in_buf) < len && s_fc41d_qble_header_at((const uint8_t *)&buf[payload_off + payload_in_buf], (uint16_t)(len - payload_off - payload_in_buf)) >= 0) { /* 下一 +QBLERECV 已到达:按多帧拼接 JSON,勿等待本帧 data_len 填满 */ BLE->io.qble_payload_need = 0U; v_fc41d_shift_rx(buf, p_len, (uint16_t)(payload_off + payload_in_buf)); } else if (payload_in_buf == 0U) { /* 仅收到头行,等待后续纯二进制载荷 */ BLE->io.qble_payload_need = (uint16_t)data_len; v_fc41d_shift_rx(buf, p_len, payload_off); #if TCP_DEBUG_EN if (s_ble_diag_trace() != 0) { printf("[FW_UPGRADE] QBLE hdr-only uuid=%s data_len=%d need=%u\r\n", uuid, data_len, (unsigned)BLE->io.qble_payload_need); } #endif } else { /* 同一 +QBLERECV 帧 UART 分片,续接剩余 payload */ BLE->io.qble_payload_need = (uint16_t)((uint16_t)data_len - payload_in_buf); v_fc41d_shift_rx(buf, p_len, len); #if TCP_DEBUG_EN if (s_ble_diag_trace() != 0) { printf("[FW_UPGRADE] QBLE uart-split uuid=%s data_len=%d in_buf=%u need=%u\r\n", uuid, data_len, (unsigned)payload_in_buf, (unsigned)BLE->io.qble_payload_need); } #endif } } (void)role; return 1; } static void v_fc41d_parse_qble_recv(uint8_t *buf, uint16_t *p_len) { while ((BLE->io.qble_payload_need == 0U) && (*p_len > 0U) && (v_fc41d_try_parse_qble_recv_at_start(buf, p_len) != 0)) { } } static void v_fc41d_parse_link_rx(void) { uint16_t len; uint16_t pos = 0U; if ((BLE->io.link_rx_acc == NULL) || (FC41D_LINK_CAP() == 0U)) { return; } if (BLE->io.qble_payload_need == 0U) { v_fc41d_qble_resync(BLE->io.link_rx_acc, &BLE->io.link_rx_acc_len); } else if (s_fc41d_looks_like_qble_at_text(BLE->io.link_rx_acc, BLE->io.link_rx_acc_len)) { BLE->io.qble_payload_need = 0U; } v_fc41d_parse_qble_recv(BLE->io.link_rx_acc, &BLE->io.link_rx_acc_len); v_fc41d_qble_payload_continue(BLE->io.link_rx_acc, &BLE->io.link_rx_acc_len); if (BLE->io.qble_payload_need > 0U) { if (s_fc41d_looks_like_qble_at_text(BLE->io.link_rx_acc, BLE->io.link_rx_acc_len)) { BLE->io.qble_payload_need = 0U; v_fc41d_parse_qble_recv(BLE->io.link_rx_acc, &BLE->io.link_rx_acc_len); } else { #if TCP_DEBUG_EN if (s_ble_diag_trace() != 0) { printf("[FW_UPGRADE] LINK-WAIT need=%u len=%u\r\n", (unsigned)BLE->io.qble_payload_need, (unsigned)BLE->io.link_rx_acc_len); s_fw_log_link_cache("link-wait"); } #endif return; } } len = BLE->io.link_rx_acc_len; pos = 0U; while (pos < len) { uint16_t line_end = pos; int found_eol = 0; while (line_end + 1U < len) { if ((BLE->io.link_rx_acc[line_end] == '\r') && (BLE->io.link_rx_acc[line_end + 1U] == '\n')) { found_eol = 1; break; } line_end++; } if (found_eol == 0) { break; } { char line[192]; uint16_t line_len = (uint16_t)(line_end - pos); if (line_len >= (uint16_t)sizeof(line)) { line_len = (uint16_t)(sizeof(line) - 1U); } memcpy(line, &BLE->io.link_rx_acc[pos], line_len); line[line_len] = '\0'; if (strncmp(line, "+QBLERECV:", 10) == 0) { break; } if (strncmp(line, "+QBLESTAT", 9) == 0) { if (strstr(line, "DISCONNECTED") != NULL) { BLE->at.pending_urc = FC41D_BLE_URC_DISCONNECTED; BT_PRINT("URC: BLE DISCONNECTED (%s)\r\n", line); } else if (strstr(line, "CONNECTED") != NULL) { BLE->at.pending_urc = FC41D_BLE_URC_CONNECTED; BT_PRINT("URC: BLE CONNECTED (%s)\r\n", line); } } else if (strncmp(line, "+QBLEPEER", 9) == 0) { BLE->at.pending_urc = FC41D_BLE_URC_CONNECTED; BT_PRINT("URC: BLE CONNECTED (%s)\r\n", line); } v_at_append_rx((const uint8_t *)line, line_len); v_at_append_rx((const uint8_t *)"\r\n", 2U); } pos = (uint16_t)(line_end + 2U); } if (pos > 0U) { v_fc41d_shift_rx(BLE->io.link_rx_acc, &BLE->io.link_rx_acc_len, pos); } if (system_filetransfer_is_active() != 0) { v_fc41d_drain_orphan_app_payload(BLE->io.link_rx_acc, &BLE->io.link_rx_acc_len); } v_fc41d_parse_qble_recv(BLE->io.link_rx_acc, &BLE->io.link_rx_acc_len); #if TCP_DEBUG_EN if (s_ble_diag_trace() != 0) { s_fw_log_link_cache("parse-done"); } #endif } static void v_fc41d_link_rx_feed(const uint8_t *data, uint16_t len) { uint16_t i; uint16_t link_cap = FC41D_LINK_CAP(); if ((data == NULL) || (len == 0U) || (BLE->io.link_rx_acc == NULL) || (link_cap == 0U)) { return; } for (i = 0U; i < len; i++) { if (BLE->io.link_rx_acc_len >= link_cap) { v_fc41d_parse_link_rx(); link_cap = FC41D_LINK_CAP(); if (BLE->io.link_rx_acc_len >= link_cap) { if (BLE->io.qble_payload_need > 0U) { BT_PRINT("link_rx full, reset qble_payload_need\r\n"); BLE->io.qble_payload_need = 0U; v_fc41d_qble_resync(BLE->io.link_rx_acc, &BLE->io.link_rx_acc_len); } else { v_fc41d_shift_rx(BLE->io.link_rx_acc, &BLE->io.link_rx_acc_len, (uint16_t)(link_cap / 2U)); v_fc41d_qble_resync(BLE->io.link_rx_acc, &BLE->io.link_rx_acc_len); } link_cap = FC41D_LINK_CAP(); } } BLE->io.link_rx_acc[BLE->io.link_rx_acc_len++] = data[i]; } v_fc41d_parse_link_rx(); } static void v_at_uart_drain_link(void) { if (u8_at_is_transparent() != 0U) { v_at_uart_drain_payload(); return; } v_at_uart_read_all(); } /** * @brief 排空 UART7 接收(AT 阶段解析 URC;透传阶段写入 app_rx) */ static void v_at_uart_read_all(void) { uint8_t chunk[128]; uint16_t n; if (u8_at_is_transparent() != 0U) { v_at_uart_drain_payload(); return; } do { n = s_fc41d_uart_drain_rx(chunk, sizeof(chunk)); if (n > 0U) { #if TCP_DEBUG_EN if (s_ble_diag_trace() != 0) { tcp_firmware_upgrade_log_buf("UART-RX", chunk, (uint32_t)n); } #endif v_fc41d_link_rx_feed(chunk, n); } } while (n == (uint16_t)sizeof(chunk)); } /** * @brief 复位 AT 层接收状态与 URC 队列(模组重启或停止 BLE 时调用) */ static void v_at_init(void) { BLE->at.rx_acc_len = 0U; BLE->at.rx_acc[0] = '\0'; BLE->io.link_rx_acc_len = 0U; BLE->io.qble_payload_need = 0U; BLE->at.pending_urc = FC41D_BLE_URC_NONE; BLE->sm.transparent = 0U; BLE->sm.gatt_fallback = 0U; s_app_rx_clear(); } /** * @brief 清空 UART 硬件 FIFO 及 BLE->at.rx_acc,避免旧应答干扰下一条 AT * @note 最多丢弃 4096 字节,防止死循环 */ static void v_at_uart_discard(void) { uint8_t scrap[64]; uint32_t total = 0U; s_fc41d_uart_discard(); while (total < 4096U) { uint16_t n = s_fc41d_uart_drain_rx(scrap, sizeof(scrap)); if (n == 0U) { break; } total += n; } BLE->at.rx_acc_len = 0U; BLE->at.rx_acc[0] = '\0'; BLE->io.link_rx_acc_len = 0U; } /** * @brief 将 AT 命令行格式化为完整发送串(含 AT 前缀与 \\r\\n) * @return 格式化长度,失败返回 <=0 */ static int s_at_format_cmd(const char *at_line, char *out, size_t out_sz) { int n; if ((at_line == NULL) || (out == NULL) || (out_sz == 0U)) { return -1; } if (strncmp(at_line, "AT", 2) == 0) { n = snprintf(out, out_sz, "%s\r\n", at_line); } else if (at_line[0] == '+') { n = snprintf(out, out_sz, "AT%s\r\n", at_line); } else { n = snprintf(out, out_sz, "AT+%s\r\n", at_line); } if ((n <= 0) || ((size_t)n >= out_sz)) { return -1; } return n; } /** * @brief 阻塞等待当前 AT 应答 OK(同时解析 +QBLERECV 写入应用缓冲) */ static int s_at_blocking_wait_ok(uint32_t wait_ms) { uint64_t deadline_ms = u64_get_current_millis() + (uint64_t)wait_ms; uint32_t poll_ms = 20U; #if TCP_DEBUG_EN if (system_filetransfer_is_active() != 0) { poll_ms = 5U; } #endif while (u64_get_current_millis() < deadline_ms) { uint8_t chunk[128]; uint16_t got = s_fc41d_uart_drain_rx(chunk, sizeof(chunk)); if (got > 0U) { #if TCP_DEBUG_EN if (s_ble_diag_trace() != 0) { tcp_firmware_upgrade_log_buf("UART-RX-AT", chunk, (uint32_t)got); } #endif v_fc41d_link_rx_feed(chunk, got); } if (s_fc41d_link_or_at_has_ok() != 0) { return 0; } if (strstr(BLE->at.rx_acc, "+QBLEMTU:") != NULL) { return 0; } if (strstr(BLE->at.rx_acc, "ERROR") != NULL) { return -1; } mSleep(poll_ms); v_wdt_setFlag(TASK_ID_TcpSer); } return -1; } /** * @brief 阻塞执行一条 AT 命令 * @return 0 成功,-1 失败 */ static int s_at_exec_blocking_line(const char *at_line, uint32_t wait_ms) { char cmd[FC41D_AT_LINE_MAX]; int cmd_len; if (at_line == NULL) { return -1; } cmd_len = s_at_format_cmd(at_line, cmd, sizeof(cmd)); if (cmd_len <= 0) { return -1; } BLE->at.rx_acc_len = 0U; BLE->at.rx_acc[0] = '\0'; v_at_uart_drain_link(); BT_PRINT("AT>> %s", cmd); if (u16_usart_send(FC41D_USART_ID, (uint8_t *)cmd, (uint16_t)cmd_len) == 0U) { BT_PRINT("AT send failed\r\n"); return -1; } if (s_at_blocking_wait_ok(wait_ms) != 0) { BT_PRINT("AT timeout blocking (rx_len=%u)\r\n", (unsigned)BLE->at.rx_acc_len); if (BLE->at.rx_acc_len > 0U) { BT_PRINT("AT rx: %s\r\n", BLE->at.rx_acc); } return -1; } return 0; } /** * @brief 取走并清零一条待处理的 BLE URC(CONNECTED/DISCONNECTED) * @note 顺带读 UART 并解析 URC / +QBLERECV */ static fc41d_ble_urc_t s_at_poll_urc(void) { fc41d_ble_urc_t ev; if (u8_at_is_transparent() == 0U) { v_at_uart_read_all(); } ev = BLE->at.pending_urc; BLE->at.pending_urc = FC41D_BLE_URC_NONE; return ev; } /** * @brief 阻塞执行一条较长 AT(GATT notify 含 hex 载荷) */ static int s_at_exec_blocking_gatt(const char *at_line, uint32_t wait_ms) { int cmd_len; if (at_line == NULL) { return -1; } cmd_len = s_at_format_cmd(at_line, BLE->at.gatt_at_cmd, sizeof(BLE->at.gatt_at_cmd)); if (cmd_len <= 0) { return -1; } BLE->at.rx_acc_len = 0U; BLE->at.rx_acc[0] = '\0'; s_ble_drain_uart_before_gatt(); if (u16_usart_send(FC41D_USART_ID, (uint8_t *)BLE->at.gatt_at_cmd, (uint16_t)cmd_len) == 0U) { BT_PRINT("AT send failed\r\n"); return -1; } if (s_at_blocking_wait_ok(wait_ms) != 0) { s_ble_drain_uart_before_gatt(); if (s_fc41d_link_or_at_has_ok() != 0) { return 0; } if (s_at_blocking_wait_ok(400U) != 0) { BT_PRINT("AT GATT timeout (rx_len=%u)\r\n", (unsigned)BLE->at.rx_acc_len); return -1; } return 0; } return 0; } /** * @brief 经 QBLEGATTSNTFY 向手机 Notify 特征发送一段载荷(hex 编码) */ static int s_ble_gatt_notify_chunk(const uint8_t *data, uint16_t len) { static const char k_hex[] = "0123456789ABCDEF"; char line[FC41D_AT_CMD_MAX]; char *hex_out; uint16_t i; int n; if ((data == NULL) || (len == 0U)) { return -1; } if ((size_t)len * 2U + 48U >= sizeof(line)) { return -1; } n = snprintf(line, sizeof(line), "QBLEGATTSNTFY=%s,%u,", FC41D_CHAR_NOTIFY_UUID, (unsigned)len); if (n <= 0) { return -1; } hex_out = &line[n]; for (i = 0U; i < len; i++) { hex_out[i * 2U] = k_hex[(data[i] >> 4) & 0x0FU]; hex_out[i * 2U + 1U] = k_hex[data[i] & 0x0FU]; } hex_out[len * 2U] = '\0'; return s_at_exec_blocking_gatt(line, FC41D_GATT_AT_WAIT_MS); } /** * @brief GATT 非透传发送(自动分片),与 WiFi/TCP 上层载荷格式一致 * @return 实际发送字节数,失败 -1 */ static int s_ble_gatt_notify_send(const uint8_t *data, uint16_t len) { uint16_t off = 0U; int total = 0; if ((data == NULL) || (len == 0U)) { return -1; } while (off < len) { uint16_t chunk = (uint16_t)(len - off); if (chunk > FC41D_GATT_TX_CHUNK) { chunk = FC41D_GATT_TX_CHUNK; } if (s_ble_gatt_notify_chunk(&data[off], chunk) != 0) { return (total > 0) ? total : -1; } off = (uint16_t)(off + chunk); total += (int)chunk; if (off < len) { mSleep(30U); } } return total; } /** * @brief 判断 FC41D 应答中是否含独立 OK(排除 +Qxxx 字段内的 ok) */ static int s_fc41d_resp_has_ok(const uint8_t *buf, uint16_t len) { uint16_t i; if (buf == NULL || len < 2U) { return 0; } for (i = 0U; i + 1U < len; ++i) { if ((buf[i] == 'O' || buf[i] == 'o') && (buf[i + 1U] == 'K' || buf[i + 1U] == 'k')) { if (i > 0U && buf[i - 1U] == '+') { continue; } return 1; } } return 0; } /** * @brief 在 AT 累积区或 link 解析缓冲中查找模组 OK(升级时 OK 常混在 +QBLERECV 流里) */ static int s_fc41d_link_or_at_has_ok(void) { uint16_t i; uint16_t n; if (s_fc41d_resp_has_ok((const uint8_t *)BLE->at.rx_acc, BLE->at.rx_acc_len) != 0) { return 1; } n = BLE->io.link_rx_acc_len; if (n < 4U) { return 0; } for (i = 0U; i + 3U < n; i++) { if ((BLE->io.link_rx_acc[i] == '\r') && (BLE->io.link_rx_acc[i + 1U] == '\n') && (BLE->io.link_rx_acc[i + 2U] == 'O') && (BLE->io.link_rx_acc[i + 3U] == 'K')) { return 1; } if ((i == 0U) && (BLE->io.link_rx_acc[0] == 'O') && (BLE->io.link_rx_acc[1] == 'K') && (BLE->io.link_rx_acc[2] == '\r') && (BLE->io.link_rx_acc[3] == '\n')) { return 1; } } return 0; } /** * @brief GATT 发送前尽量排空入站数据,降低与 +QBLERECV 并发冲突 */ static void s_ble_drain_uart_before_gatt(void) { #if TCP_DEBUG_EN if (system_filetransfer_is_active() != 0) { uint8_t round; for (round = 0U; round < 6U; round++) { v_at_uart_read_all(); } return; } #endif v_at_uart_drain_link(); } static int s_ble_is_periodic_report_json(const uint8_t *data, uint32_t len) { if ((data == NULL) || (len < 16U)) { return 0; } if (strstr((const char *)data, "\"command\":\"SystemInfo\"") != NULL) { return 1; } if (strstr((const char *)data, "\"command\":\"GunInfo\"") != NULL) { return 1; } return 0; } static void s_ble_set_peer_info(void) { (void)snprintf(SESS->peer_info, sizeof(SESS->peer_info), "BLE:%s", FC41D_BLE_NAME); } /* ============================================================================= * 硬件控制 — 静态函数(PE6 = FC41D_RST,高电平复位,常态低) * ============================================================================= */ /** * @brief FC41D 硬件复位脉冲:高 200ms 后释放为低,并清空 UART7 接收 */ static void v_hw_fc41d_reset_pulse(void) { FC41D_RST(1); mSleep(FC41D_RST_PULSE_MS); FC41D_RST(0); s_fc41d_uart_init(); s_fc41d_uart_discard(); BT_PRINT("FC41D reset pulse done (PE6 RST)\r\n"); } /** * @brief 拉高 PE6 使模组进入复位 */ static void v_hw_fc41d_hold_reset(void) { FC41D_RST(1); BT_PRINT("FC41D held in reset\r\n"); } /* ============================================================================= * BLE 状态机 — 静态函数 * ============================================================================= */ /** * @brief 阻塞执行 BLE 外设初始化 AT 序列 * @return 0 成功,-1 失败 */ static int s_ble_setup_sequence_blocking(void) { if (s_at_exec_blocking_line("QBLEINIT=2", FC41D_AT_INIT_WAIT_MS) != 0) { return -1; } (void)snprintf(BLE->at.ble_cmd_buf, sizeof(BLE->at.ble_cmd_buf), "QBLENAME=%s", FC41D_BLE_NAME); if (s_at_exec_blocking_line(BLE->at.ble_cmd_buf, FC41D_AT_CMD_WAIT_MS) != 0) { return -1; } if (s_at_exec_blocking_line("QBLEURC=2", FC41D_AT_CMD_WAIT_MS) != 0) { return -1; } (void)snprintf(BLE->at.ble_cmd_buf, sizeof(BLE->at.ble_cmd_buf), "QBLEGATTSSRV=%s", FC41D_SRV_UUID); if (s_at_exec_blocking_line(BLE->at.ble_cmd_buf, FC41D_AT_CMD_WAIT_MS) != 0) { return -1; } (void)snprintf(BLE->at.ble_cmd_buf, sizeof(BLE->at.ble_cmd_buf), "QBLEGATTSCHAR=%s", FC41D_CHAR_WRITE_UUID); if (s_at_exec_blocking_line(BLE->at.ble_cmd_buf, FC41D_AT_CMD_WAIT_MS) != 0) { return -1; } (void)snprintf(BLE->at.ble_cmd_buf, sizeof(BLE->at.ble_cmd_buf), "QBLEGATTSCHAR=%s", FC41D_CHAR_NOTIFY_UUID); if (s_at_exec_blocking_line(BLE->at.ble_cmd_buf, FC41D_AT_CMD_WAIT_MS) != 0) { return -1; } if (s_at_exec_blocking_line("QBLEADVPARAM=150,150", FC41D_AT_CMD_WAIT_MS) != 0) { return -1; } if (s_at_exec_blocking_line("QBLEADVSTART", FC41D_AT_CMD_WAIT_MS) != 0) { return -1; } BT_PRINT("BLE advertising started, name=%s\r\n", FC41D_BLE_NAME); return 0; } /** * @brief 阻塞查询 QBLESTAT,判断是否已连接(10s 兜底,非每周期轮询) * @return 1 已连接,0 未连接或查询失败 */ static int s_fc41d_stat_query_connected(void) { static const char k_cmd[] = "AT+QBLESTAT\r\n"; uint8_t rx[320]; uint64_t deadline_ms; uint16_t rx_len; s_fc41d_uart_discard(); if (u16_usart_send(FC41D_USART_ID, (uint8_t *)k_cmd, (uint16_t)(sizeof(k_cmd) - 1U)) == 0U) { return 0; } rx_len = 0U; rx[0] = '\0'; deadline_ms = u64_get_current_millis() + (uint64_t)FC41D_AT_CMD_WAIT_MS; while (u64_get_current_millis() < deadline_ms) { uint16_t got; got = s_fc41d_uart_drain_rx(&rx[rx_len], (uint16_t)(sizeof(rx) - rx_len)); if (got > 0U) { rx_len += got; rx[rx_len] = '\0'; } if (strstr((char *)rx, "ERROR") != NULL) { return 0; } if (s_fc41d_resp_has_ok(rx, rx_len) != 0) { if (strstr((char *)rx, "CONNECTED") != NULL && strstr((char *)rx, "DISCONNECTED") == NULL) { return 1; } return 0; } mSleep(20U); v_wdt_setFlag(TASK_ID_TcpSer); } return 0; } /** * @brief 连接后进入 QBLETRANMODE 透传(手册 AT+QBLETRANMODE) */ static int s_ble_enter_transparent_blocking(void) { #if !FC41D_BLE_USE_TRANSPARENT if (s_at_exec_blocking_line("QBLECFGMTU=512", FC41D_AT_CMD_WAIT_MS) != 0) { BT_PRINT("QBLECFGMTU failed (continue)\r\n"); } BLE->sm.gatt_fallback = 1U; return 0; #else static const char *const k_tran_cmds[] = { "QBLETRANMODE=" FC41D_TRAN_UUID ",1", "QBLETRANMODE=" FC41D_CHAR_NOTIFY_UUID ",1", "QBLETRANMODE=" FC41D_CHAR_WRITE_UUID ",1", "QBLETRANMODE=" FC41D_TRAN_UUID ",0", "QBLETRANMODE=" FC41D_CHAR_NOTIFY_UUID ",0", }; size_t i; if (s_at_exec_blocking_line("QBLECFGMTU=512", FC41D_AT_CMD_WAIT_MS) != 0) { BT_PRINT("QBLECFGMTU failed (continue)\r\n"); } for (i = 0U; i < (sizeof(k_tran_cmds) / sizeof(k_tran_cmds[0])); i++) { if (s_at_exec_blocking_line(k_tran_cmds[i], FC41D_AT_CMD_WAIT_MS) == 0) { v_at_set_transparent(1U); BLE->sm.gatt_fallback = 0U; BT_PRINT("Transparent mode OK (%s)\r\n", k_tran_cmds[i]); return 0; } BT_PRINT("QBLETRANMODE failed: %s\r\n", k_tran_cmds[i]); } BLE->sm.gatt_fallback = 1U; v_at_set_transparent(0U); BT_PRINT("QBLETRANMODE all failed, GATT fallback (+QBLERECV)\r\n"); return 0; #endif } static int s_ble_exit_transparent_blocking(void) { #if FC41D_BLE_USE_TRANSPARENT if (u8_at_is_transparent() == 0U) { return 0; } v_at_set_transparent(0U); (void)snprintf(BLE->at.ble_cmd_buf, sizeof(BLE->at.ble_cmd_buf), "QBLETRANMODE=%s,0", FC41D_CHAR_NOTIFY_UUID); if (s_at_exec_blocking_line(BLE->at.ble_cmd_buf, FC41D_AT_CMD_WAIT_MS) != 0) { BT_PRINT("QBLETRANMODE exit failed\r\n"); return -1; } #endif return 0; } /** * @brief 丢弃 UART 残留并清空 AT 接收缓冲(断连/停止前调用) */ static void v_at_tx_reset(void) { v_at_uart_discard(); } /** * @brief 复位 BLE 会话全部软件状态(不断电) */ static void v_ble_session_reset(void) { BLE->sm.state = FC41D_BLE_ST_IDLE; SESS->client_connected = 0U; BLE->sm.connect_deadline_ms = 0U; BLE->sm.power_on_deadline_ms = 0U; BLE->sm.last_stat_poll_ms = 0U; BLE->sm.gatt_fallback = 0U; SESS->peer_info[0] = '\0'; v_at_init(); s_ble_io_free(); v_at_tx_reset(); } /** * @brief 上电并开始 BLE 初始化状态机(异步,不阻塞 tcp_ser 任务) * @return 0 成功 */ static int s_ble_session_start(void) { v_ble_session_reset(); if (s_ble_io_set_caps(FC41D_LINK_RX_NORMAL, FC41D_APP_RX_NORMAL) != 0) { BT_PRINT("BLE RX normal buf alloc failed\r\n"); return -1; } v_hw_fc41d_reset_pulse(); BLE->sm.power_on_deadline_ms = u64_get_current_millis() + (uint64_t)FC41D_POWER_ON_DELAY_MS; BLE->sm.state = FC41D_BLE_ST_POWER_ON; BT_PRINT("BLE init started (DIP1=%u)\r\n", (unsigned)u8_sub_comm_yx_get(E_SUB_DIP_1)); return 0; } /** * @brief 驱动 BLE 状态机(需在 comm_link_ble_process / 任务循环中周期调用) */ static void v_ble_session_process(void) { fc41d_ble_urc_t urc; uint64_t now_ms; if (BLE->sm.state == FC41D_BLE_ST_IDLE || BLE->sm.state == FC41D_BLE_ST_ERROR) { return; } now_ms = u64_get_current_millis(); urc = s_at_poll_urc(); if (BLE->sm.state == FC41D_BLE_ST_POWER_ON) { if (u64_get_current_millis() < BLE->sm.power_on_deadline_ms) { return; } if (s_ble_setup_sequence_blocking() != 0) { BT_PRINT("BLE setup failed\r\n"); BLE->sm.state = FC41D_BLE_ST_ERROR; return; } BLE->sm.state = FC41D_BLE_ST_WAIT_CONNECT; BLE->sm.connect_deadline_ms = u64_get_current_millis() + (uint64_t)FC41D_CONNECT_WAIT_MS; BLE->sm.last_stat_poll_ms = u64_get_current_millis(); BT_PRINT("Waiting for BLE connection...\r\n"); return; } if (BLE->sm.state == FC41D_BLE_ST_WAIT_CONNECT) { if (urc == FC41D_BLE_URC_CONNECTED) { BT_PRINT("BLE link connected (URC)\r\n"); SESS->client_connected = 1U; s_ble_set_peer_info(); (void)s_ble_enter_transparent_blocking(); BLE->sm.state = FC41D_BLE_ST_CONNECTED; if (u8_at_is_transparent() != 0U) { BT_PRINT("BLE connected, name=%s\r\n", FC41D_BLE_NAME); BT_PRINT("BLE transparent ready for JSON\r\n"); } else { BT_PRINT("BLE connected (GATT), name=%s\r\n", FC41D_BLE_NAME); } return; } if ((now_ms - BLE->sm.last_stat_poll_ms) >= (uint64_t)FC41D_STAT_POLL_MS) { BLE->sm.last_stat_poll_ms = now_ms; if (s_fc41d_stat_query_connected() != 0) { BT_PRINT("BLE link connected (QBLESTAT)\r\n"); SESS->client_connected = 1U; s_ble_set_peer_info(); (void)s_ble_enter_transparent_blocking(); BLE->sm.state = FC41D_BLE_ST_CONNECTED; if (u8_at_is_transparent() != 0U) { BT_PRINT("BLE connected, name=%s\r\n", FC41D_BLE_NAME); BT_PRINT("BLE transparent ready for JSON\r\n"); } else { BT_PRINT("BLE connected (GATT), name=%s\r\n", FC41D_BLE_NAME); } return; } } if (now_ms >= BLE->sm.connect_deadline_ms) { /* 已在广播中,勿重复 QBLEADVSTART(模组会回 ERROR) */ BT_PRINT("BLE still advertising, waiting for phone connect...\r\n"); BLE->sm.connect_deadline_ms = now_ms + (uint64_t)FC41D_CONNECT_WAIT_MS; BLE->sm.last_stat_poll_ms = now_ms; } return; } if (BLE->sm.state == FC41D_BLE_ST_CONNECTED) { if (u8_at_is_transparent() != 0U) { v_at_uart_drain_payload(); } else { #if TCP_DEBUG_EN if (system_filetransfer_is_active() != 0) { uint8_t drain; for (drain = 0U; drain < 16U; drain++) { v_at_uart_drain_link(); } } else #endif { v_at_uart_drain_link(); } } if (urc == FC41D_BLE_URC_DISCONNECTED) { SESS->client_connected = 0U; SESS->peer_info[0] = '\0'; (void)s_ble_exit_transparent_blocking(); BLE->sm.gatt_fallback = 0U; s_app_rx_clear(); BLE->sm.state = FC41D_BLE_ST_WAIT_CONNECT; BLE->sm.connect_deadline_ms = now_ms + (uint64_t)FC41D_CONNECT_WAIT_MS; BLE->sm.last_stat_poll_ms = now_ms; (void)s_at_exec_blocking_line("QBLEADVSTOP", FC41D_AT_CMD_WAIT_MS); if (s_at_exec_blocking_line("QBLEADVSTART", FC41D_AT_CMD_WAIT_MS) != 0) { BT_PRINT("BLE re-advertise failed\r\n"); } else { BT_PRINT("BLE disconnected, re-advertising\r\n"); } } } } /** * @brief 是否已有 BLE 逻辑连接(不含透传是否就绪) */ static uint8_t u8_ble_is_connected(void) { return SESS->client_connected; } /* ============================================================================= * 对外接口 — DIP 拨码 * ============================================================================= */ /** * @brief 上电后等待一段时间,确保副板 sub_comm 已上报 DIP 状态 * @param timeout_ms 阻塞等待毫秒数(当前实现为固定 delay,便于日志打印 DIP1) * @return 固定 0 */ int ble_link_wait_dip_ready(uint32_t timeout_ms) { uint64_t deadline = u64_get_current_millis() + (uint64_t)timeout_ms; while (u64_get_current_millis() < deadline) { mSleep(50U); } BT_PRINT("DIP wait %ums, DIP1=%u\r\n", (unsigned)timeout_ms, (unsigned)u8_sub_comm_yx_get(E_SUB_DIP_1)); return 0; } /** * @brief 根据 DIP1 判断当前是否应选择蓝牙调试链路 * @return 1 DIP1=1(蓝牙),0 DIP1=0(以太网 TCP) */ uint8_t ble_link_is_bt_mode(void) { return (u8_sub_comm_yx_get(E_SUB_DIP_1) != 0U) ? 1U : 0U; } /* ============================================================================= * 对外接口 — comm_link 后端(与 tcp_server JSON 协议对接) * ============================================================================= */ /** * @brief 启动 BLE 后端:模组上电并开始 AT 初始化序列 * @return 0 成功 */ int comm_link_ble_start(void) { return s_ble_session_start(); } /** * @brief 停止 BLE:复位状态机、模组下电 */ void comm_link_ble_stop(void) { (void)s_ble_exit_transparent_blocking(); v_at_tx_reset(); v_ble_session_reset(); v_hw_fc41d_hold_reset(); } /** * @brief 周期处理:推进 BLE 状态机(广播、连接、断线重广播) */ void comm_link_ble_process(void) { v_ble_session_process(); } /** * @brief 是否有上位机连接且已进入透传(tcp_server 可收发 JSON) * @return 1 已连接且透传就绪,0 否 */ int comm_link_ble_is_client_connected(void) { if (u8_ble_is_connected() == 0U) { return 0; } if (BLE->sm.state != FC41D_BLE_ST_CONNECTED) { return 0; } #if FC41D_BLE_USE_TRANSPARENT return (u8_at_is_transparent() != 0U || BLE->sm.gatt_fallback != 0U) ? 1 : 0; #else return 1; #endif } /** * @brief 主动断开当前 BLE 连接(发送 QBLEDISCONN) */ void comm_link_ble_close_client(void) { (void)s_ble_exit_transparent_blocking(); BLE->sm.gatt_fallback = 0U; v_at_tx_reset(); s_app_rx_clear(); (void)s_at_exec_blocking_line("QBLEDISCONN", FC41D_AT_CMD_WAIT_MS); } /** * @brief 从 BLE 透传通道读取数据(QBLETRANMODE 后 UART 直传 JSON) * @return >0 字节数,0 无数据,-1 错误 */ int comm_link_ble_receive_data(uint8_t *buffer, uint32_t buffer_size) { if ((buffer == NULL) || (buffer_size == 0U)) { return -1; } if (BLE->sm.state != FC41D_BLE_ST_CONNECTED) { return 0; } #if FC41D_BLE_USE_TRANSPARENT if ((u8_at_is_transparent() == 0U) && (BLE->sm.gatt_fallback == 0U)) { return 0; } #endif #if TCP_DEBUG_EN if (system_filetransfer_is_active() != 0) { uint8_t pass; for (pass = 0U; pass < 16U; pass++) { v_at_uart_read_all(); } } else #endif { v_at_uart_read_all(); } { int n = s_app_rx_pop(buffer, buffer_size); #if TCP_DEBUG_EN if (n > 0) { s_ble_log_json_io("RX", buffer, (uint32_t)n); } if ((s_ble_diag_trace() != 0) && (n > 0)) { tcp_firmware_upgrade_log_buf("APP-POP", buffer, (uint32_t)n); printf("[FW_UPGRADE] APP-POP done n=%d app_remain=%u\r\n", n, (unsigned)s_app_rx_pending()); } else if ((s_ble_diag_trace() != 0) && (s_app_rx_pending() > 0U)) { printf("[FW_UPGRADE] APP-POP blocked pending=%u cap_out=%u\r\n", (unsigned)s_app_rx_pending(), (unsigned)buffer_size); } #endif return n; } } int comm_link_ble_rx_upgrade_begin(void) { BT_PRINT("BLE RX upgrade buf (link=%u app=%u)\r\n", (unsigned)FC41D_LINK_RX_UPGRADE, (unsigned)FC41D_APP_RX_UPGRADE); if (s_ble_io_set_caps(FC41D_LINK_RX_UPGRADE, FC41D_APP_RX_UPGRADE) != 0) { return -1; } s_app_rx_clear(); return 0; } void comm_link_ble_rx_flush_pending(void) { s_app_rx_clear(); if (BLE->io.link_rx_acc != NULL) { BLE->io.link_rx_acc_len = 0U; BLE->io.qble_payload_need = 0U; } } int comm_link_ble_rx_upgrade_end(void) { BT_PRINT("BLE RX restore normal buf (link=%u app=%u)\r\n", (unsigned)FC41D_LINK_RX_NORMAL, (unsigned)FC41D_APP_RX_NORMAL); return s_ble_io_set_caps(FC41D_LINK_RX_NORMAL, FC41D_APP_RX_NORMAL); } /** * @brief 经 BLE 透传发送数据(tcp_server 应答 JSON) * @return 发送字节数,失败 -1 */ int comm_link_ble_send_data(const uint8_t *data, uint32_t len) { uint32_t off = 0U; int total = 0; if (BLE->sm.state != FC41D_BLE_ST_CONNECTED) { return -1; } if ((data == NULL) || (len == 0U)) { return -1; } if (len > 0xFFFFU) { len = 0xFFFFU; } #if TCP_DEBUG_EN if ((system_filetransfer_is_active() != 0) && (s_ble_is_periodic_report_json(data, len) != 0)) { return (int)len; } #endif #if FC41D_BLE_USE_TRANSPARENT if (u8_at_is_transparent() != 0U) { while (off < len) { uint16_t chunk = (uint16_t)(len - off); if (chunk > 512U) { chunk = 512U; } if (u16_usart_send(FC41D_USART_ID, (uint8_t *)(data + off), chunk) != chunk) { return (total > 0) ? total : -1; } total += (int)chunk; off += (uint32_t)chunk; } #if TCP_DEBUG_EN s_ble_log_json_io("TX", data, len); #endif return total; } if (BLE->sm.gatt_fallback == 0U) { return -1; } #endif { int sent = s_ble_gatt_notify_send(data, (uint16_t)len); #if TCP_DEBUG_EN if (sent > 0) { s_ble_log_json_io("TX", data, (uint32_t)sent); } #endif return (sent > 0) ? sent : -1; } } /** * @brief 填充对端描述字符串(BLE 无 IP,固定为 "BLE") * @return 0 成功,-1 参数无效 */ int comm_link_ble_get_peer_info(char *buf, uint32_t buf_size) { if ((buf == NULL) || (buf_size == 0U)) { return -1; } if (SESS->peer_info[0] != '\0') { (void)snprintf(buf, buf_size, "%s", SESS->peer_info); } else { (void)snprintf(buf, buf_size, "BLE"); } return 0; } #if !TCP_DEBUG_EN int comm_link_ble_diag_trace(void) { return 0; } #endif #endif /* BLE_DEBUG_EN */