From: 孙春阳 Date: Sun, 2 Nov 2025 13:57:14 +0000 (+0800) Subject: N706A AT上网中间版 X-Git-Url: http://git.snicker.com.cn/css/static/gitweb.css?a=commitdiff_plain;h=7cc3b869266f1bb54b5db208f0b7bdc0005cf8d5;p=mirrors%2FFD230.git N706A AT上网中间版 --- diff --git a/code/sdk/application/brandy/brandy_standard/service_init.c b/code/sdk/application/brandy/brandy_standard/service_init.c index 0c7b0c804..4cc117a8c 100644 --- a/code/sdk/application/brandy/brandy_standard/service_init.c +++ b/code/sdk/application/brandy/brandy_standard/service_init.c @@ -74,6 +74,9 @@ #ifdef SUPPORT_AT_PPP #include "ppp_at_cmd.h" #endif +#ifdef SUPPORT_AT_N706A_NET +#include "n706a_net_at_cmd.h" +#endif #include "log_file.h" #ifdef CONFIG_OTA_UPDATE_SUPPORT #include "upg.h" @@ -191,6 +194,12 @@ static void register_at_service(void) at_cmd_add_moudle(ppp_table, ppp_cmd_count); #endif +#ifdef SUPPORT_AT_N706A_NET + uint32_t n706a_net_cmd_count; + at_cmd_table_t *n706a_net_table = get_n706a_net_cmd_table(&n706a_net_cmd_count); + at_cmd_add_moudle(n706a_net_table, n706a_net_cmd_count); +#endif + #ifdef SUPPORT_ZHIHUI_AI_OLD uint32_t ai_old_cmd_count; at_cmd_table_t *ai_old_table = get_ai_old_cmd_table(&ai_old_cmd_count); diff --git a/code/sdk/bootloader/flashboot/brandy/main.c b/code/sdk/bootloader/flashboot/brandy/main.c index 9d0b4a94f..9a039a4f8 100644 --- a/code/sdk/bootloader/flashboot/brandy/main.c +++ b/code/sdk/bootloader/flashboot/brandy/main.c @@ -794,20 +794,6 @@ static void fs_init(void) uapi_tcxo_delay_ms(50); #endif -#ifdef BOARD_LEINIAO - uapi_gpio_set_dir(S_AGPIO_L32, GPIO_DIRECTION_OUTPUT); - uapi_gpio_set_val(S_AGPIO_L32, GPIO_LEVEL_HIGH); - - uapi_gpio_set_dir(S_AGPIO_R0, GPIO_DIRECTION_OUTPUT); - uapi_gpio_set_val(S_AGPIO_R0, GPIO_LEVEL_HIGH); - - uapi_gpio_set_dir(S_HGPIO4, GPIO_DIRECTION_OUTPUT); - uapi_gpio_set_val(S_HGPIO4, GPIO_LEVEL_LOW); - - uapi_gpio_set_dir(S_HGPIO0, GPIO_DIRECTION_OUTPUT); - uapi_gpio_set_val(S_HGPIO0, GPIO_LEVEL_LOW); -#endif - #ifdef CFG_DRIVERS_NANDFLASH nandflash_qspi_init(); #endif @@ -834,6 +820,31 @@ void main(void) #endif chip_init(rom_codeloadered); + + +/************************雷鸟马达控制********************* */ +#ifdef BOARD_LEINIAO +/* +马达1 +双IO 正反转控制,默认低双电平或者双高电平 马达不转,AGPIOL32 高,AGPIOR0 低,马达正转,AGPIOL32 低,AGPIOR0 高,马达反转, +*/ + uapi_gpio_set_dir(S_AGPIO_L32, GPIO_DIRECTION_OUTPUT); + uapi_gpio_set_val(S_AGPIO_L32, GPIO_LEVEL_HIGH); + + uapi_gpio_set_dir(S_AGPIO_R0, GPIO_DIRECTION_OUTPUT); + uapi_gpio_set_val(S_AGPIO_R0, GPIO_LEVEL_HIGH); + +/* +马达2 +双IO 正反转控制,默认低双电平或者双高电平 马达不转,HGPIO4 高,HGPIO0 低,马达正转,HGPIO4 低,HGPIO0 高,马达反转, +*/ + uapi_gpio_set_dir(S_HGPIO4, GPIO_DIRECTION_OUTPUT); + uapi_gpio_set_val(S_HGPIO4, GPIO_LEVEL_LOW); + + uapi_gpio_set_dir(S_HGPIO0, GPIO_DIRECTION_OUTPUT); + uapi_gpio_set_val(S_HGPIO0, GPIO_LEVEL_LOW); +#endif + flash_init(); while (uapi_uart_has_pending_transmissions(SW_DEBUG_UART_BUS)) { } diff --git a/code/sdk/build/config/target_config/brandy/config.py b/code/sdk/build/config/target_config/brandy/config.py index 5b68f44e8..dad8a9934 100644 --- a/code/sdk/build/config/target_config/brandy/config.py +++ b/code/sdk/build/config/target_config/brandy/config.py @@ -1420,8 +1420,6 @@ target = { 'ENABLE_CUSTOM_I2C', #'ENABLE_VIBRATOR', # 'ENCODER_SUPPORT', - # 'SUPPORT_SPO2_NOTIFICATION', - # 'SUPPORT_SPO2_NOTIFICATION_TEST', 'SUPPORT_DEVICE_PAIRING_NOTIFICATION', # 'ENABLE_FONT_VECTOR_GLOBAL', # 'ENABLE_SHAPING', @@ -1429,7 +1427,6 @@ target = { # 'SUPPORT_AT_CUSTOMUTILS', # 'SUPPORT_BLE_SERVICE', # 'SUPPORT_MAG_SERVICE', - # 'SUPPORT_PPG_SERVICE', # 'SUPPORT_CYWEE_SERVICE', 'SUPPORT_BATTERY_SERVICE', 'LEINIAO', @@ -1468,20 +1465,18 @@ target = { ], #添加自定义应用 'ram_component': [ - 'audio_at_service', - 'media_at_service', - 'bt_manager_at_service', - # "ppg_sensor_hx3695", - # "ppgBleService", - # "mag_sensor_mmc5603", - "vibrator", - "vibrator_service", - # "-:lwip", - "thunderbird_toys",#雷鸟 - "leiniaoMediaService", - #cat1 相关配置 - #有方需要开启 PPP - "ppp", + 'audio_at_service', + 'media_at_service', + 'bt_manager_at_service', + "vibrator", + "vibrator_service", + "thunderbird_toys",#雷鸟 + "leiniaoMediaService", + #cat1 相关配置 + #有方需要开启 ppp和n706a_net 两个网络库之一 + # 注意:n706a_net 需要 ppp 组件提供 runtime 更新函数 + # "ppp", + "n706a_net", #EG810 需要开启 cat1Service 和 cat1Test # "cat1Service", @@ -1494,8 +1489,7 @@ target = { #zhihui_net 相关配置 "zhihui_net", - "n706a_net", - + #ai 相关配置 "aiMediaService", "aiService", @@ -1509,37 +1503,28 @@ target = { "imsl", "minimp3", - "nativeabilityfwk", - "custom_utils", - "custom_utilscpp", - "custom_config", - # "rotary_encoder", - "nativelauncher", - # "ppg_service", - # "cywee_service", - "batterry_service", - # "bleservice", - # "blemoder", - "transspecard", - # "factorytest_service", - # "hid_service", - # "producttest", - # "ppg_hal", - # "mag_service", - # "cywee_dml", - #"cyweeBleService", - 'AT_factory_test', - "-:alipay_soc", - "-:alipay_soc_adapt", - "-:alipay_service", - "-:alipay_hal", - "-:alipay_antchain", - "-:alipay_msg", - ], + "nativeabilityfwk", + "custom_utils", + "custom_utilscpp", + "custom_config", + "nativelauncher", + "batterry_service", + # "bleservice", + # "blemoder", + "transspecard", + # "factorytest_service", + # "hid_service", + 'AT_factory_test', + "-:alipay_soc", + "-:alipay_soc_adapt", + "-:alipay_service", + "-:alipay_hal", + "-:alipay_antchain", + "-:alipay_msg", + ], 'image_analysis': False, 'fs_image_path':'leiniao', 'ssb_version': 'brandy-native-js-ln-ssb', - # 'fs_image_path':'evb', 'fs_image': True }, 'brandy-native-js-lnppp-r': { @@ -1551,14 +1536,13 @@ target = { # 'NANDFLASH_F35UQA', 'NANDFLASH_GD5F2G', # 'NANDFLASH_XT26Q02D', - # 'TPTYPE_FT6X46', 'CFG_FLASH_SIZE=16', 'CONFIG_NORFLASH_SIZE=16', 'CUSTOM_UTILS', 'LEINIAO', 'SINGLE_USER_PARTITION', - # 添加显示支持 - 'LCD_JD9885', + # 添加显示支持 + 'LCD_JD9885', ], 'ram_component': ["at_cmd"] }, diff --git a/code/sdk/zhihui/CMakeLists.txt b/code/sdk/zhihui/CMakeLists.txt index 8195aac07..1d56c5a4e 100644 --- a/code/sdk/zhihui/CMakeLists.txt +++ b/code/sdk/zhihui/CMakeLists.txt @@ -33,3 +33,87 @@ add_subdirectory_if_exist(minimp3) add_subdirectory_if_exist(shake) add_subdirectory_if_exist(n706a_net) add_subdirectory_if_exist(zhihui_net) + + +# ========== PPP 和 N706A_NET 强制互斥检测 ========== +# PPP 和 N706A_NET 使用同一个 UART 但工作模式不同,不能同时启用 + +# 检测 SUPPORT_PPP_SERVICE_* 和 SUPPORT_N706A_NET 宏定义 +set(HAS_PPP_SERVICE FALSE) +set(HAS_N706A_NET FALSE) + +# 检查 SUPPORT_PPP_SERVICE(从config.py的defines传递) +if("SUPPORT_PPP_SERVICE" IN_LIST DEFINES OR "ppp" IN_LIST RAM_COMPONENT) + set(HAS_PPP_SERVICE TRUE) + if(NOT "SUPPORT_PPP_SERVICE" IN_LIST DEFINES) + list(APPEND DEFINES "SUPPORT_PPP_SERVICE") + message(STATUS "Auto-detected ppp component, added SUPPORT_PPP_SERVICE to DEFINES") + endif() +endif() + +# 检查 SUPPORT_N706A_NET(支持两种方式检测) +# 方式1:从config.py的defines传递(推荐) +# 方式2:从ram_component检测组件是否被引入 +if("SUPPORT_N706A_NET" IN_LIST DEFINES OR "n706a_net" IN_LIST RAM_COMPONENT) + set(HAS_N706A_NET TRUE) + # 确保DEFINES中也有此宏(供其他地方使用,如互斥检测) + if(NOT "SUPPORT_N706A_NET" IN_LIST DEFINES) + list(APPEND DEFINES "SUPPORT_N706A_NET") + message(STATUS "Auto-detected n706a_net component, added SUPPORT_N706A_NET to DEFINES") + endif() +endif() + +# 强制互斥检测:如果同时定义则终止编译 +if(HAS_PPP_SERVICE AND HAS_N706A_NET) + message(FATAL_ERROR " +================================================================================ +【致命错误】网络组件冲突 - 编译终止! +================================================================================ + +SUPPORT_PPP_SERVICE 和 SUPPORT_N706A_NET 是互斥的网络实现方式,不能同时启用! + +当前配置错误: + ✗ SUPPORT_PPP_SERVICE = 已定义 + ✗ SUPPORT_N706A_NET = 已定义 + +这两个宏不能同时存在! + +原因: + - 两者都使用同一个 UART (UART_BUS_0) 进行 4G 通信 + - PPP 使用 PPP 协议拨号上网(需要 AT/PPP 模式切换) + - N706A_NET 使用纯 AT 命令实现网络通信(始终 AT 模式) + - 同时启用会导致 UART 访问冲突和通信失败 + +解决方法: + 请在 config.py 的 'defines' 中只保留其中一个: + + 方案A - 使用 PPP 拨号上网: + 'defines': [ + 'SUPPORT_PPP_SERVICE_INTERRUPT', # 或 SUPPORT_PPP_SERVICE_POLLING + # 'SUPPORT_N706A_NET', # 注释掉此行 + ... + ], + 'ram_component': ['ppp', 'zhihui_net', ...] + + 方案B - 使用 N706A AT 网络: + 'defines': [ + # 'SUPPORT_PPP_SERVICE_INTERRUPT', # 注释掉此行 + 'SUPPORT_N706A_NET', + ... + ], + 'ram_component': ['n706a_net', 'zhihui_net', ...] + +请修改配置后重新编译! +================================================================================ +") +endif() + +# 提示当前使用的网络模式 +if(HAS_PPP_SERVICE) + message(STATUS "【网络模式】PPP Service (${PPP_MODE} mode)") +elseif(HAS_N706A_NET) + message(STATUS "【网络模式】N706A AT Network (Pure AT mode)") +else() + message(STATUS "【网络模式】No network mode selected") +endif() + diff --git a/code/sdk/zhihui/leiniao/imsl_ln/ln_api.c b/code/sdk/zhihui/leiniao/imsl_ln/ln_api.c index f559259d3..6b864e1dd 100644 --- a/code/sdk/zhihui/leiniao/imsl_ln/ln_api.c +++ b/code/sdk/zhihui/leiniao/imsl_ln/ln_api.c @@ -1739,20 +1739,13 @@ errcode_t uapi_ln_http_server_init(void) { printf("uapi_ln_http_server_init !\r\n"); - /* 初始化 zhihui_net - * 说明: - * - PPP拨号模式:使用 OpenHarmony (PPP + lwIP + haisi_websocket) - * - 非PPP模式:使用 N706A AT指令(纯AT,不拨号) + /* 初始化 zhihui_net + * 模式由 SUPPORT_N706A_NET 宏自动决定: + * - 定义了 SUPPORT_N706A_NET → N706A AT 模式(不拨号PPP) + * - 未定义 SUPPORT_N706A_NET → OpenHarmony 模式(PPP拨号 + lwIP) + * + * 无需手动调用 ZhihuiNetSetImplementation(),已自动配置 */ - #if defined(SUPPORT_PPP_SERVICE_INTERRUPT) || defined(SUPPORT_PPP_SERVICE_POLLING) - /* PPP拨号模式:使用OpenHarmony实现(基于lwIP) */ - ZhihuiNetSetImplementation(ZHIHUI_NET_IMPL_OPENHARMONY); - printf("[LeiNiao] Using OpenHarmony (PPP + lwIP) network implementation\r\n"); - #else - /* 非PPP模式:使用N706A AT指令 */ - ZhihuiNetSetImplementation(ZHIHUI_NET_IMPL_N706A_AT); - printf("[LeiNiao] Using N706A AT command network implementation\r\n"); - #endif ZhihuiNetInit(); ln_send_taskcfg.create_queue_handle = create_ln_http_send_queue; diff --git a/code/sdk/zhihui/n706a_net/CMakeLists.txt b/code/sdk/zhihui/n706a_net/CMakeLists.txt index 9d74e569b..7c08449f7 100644 --- a/code/sdk/zhihui/n706a_net/CMakeLists.txt +++ b/code/sdk/zhihui/n706a_net/CMakeLists.txt @@ -1,5 +1,5 @@ #=============================================================================== -# @brief N706A PPP网络接口层 +# @brief N706A AT网络接口层(独立于PPP) # Copyright (c) @CompanyNameMagicTag 2023-2025. All rights reserved. #=============================================================================== set(COMPONENT_NAME "n706a_net") @@ -8,6 +8,16 @@ set(SOURCES ${SOURCES} ${CMAKE_CURRENT_SOURCE_DIR}/src/n706a_tcp_client.c ${CMAKE_CURRENT_SOURCE_DIR}/src/n706a_http_client.c ${CMAKE_CURRENT_SOURCE_DIR}/src/n706a_websocket.c + ${CMAKE_CURRENT_SOURCE_DIR}/src/n706a_at_handler.c + ${CMAKE_CURRENT_SOURCE_DIR}/src/n706a_net_api.c + ${CMAKE_CURRENT_SOURCE_DIR}/src/ppp_interrupt.c + ${CMAKE_CURRENT_SOURCE_DIR}/src/ppp_serial.c + ${CMAKE_CURRENT_SOURCE_DIR}/src/ppp_power_ctl.c + ${CMAKE_CURRENT_SOURCE_DIR}/src/ppp_cat1_api.c + ${CMAKE_CURRENT_SOURCE_DIR}/src/ppp_debug.c + ${CMAKE_CURRENT_SOURCE_DIR}/src/ppp_debug_manager.c + ${CMAKE_CURRENT_SOURCE_DIR}/example/n706a_net_example.c + ${CMAKE_CURRENT_SOURCE_DIR}/example/n706a_net_at_cmd.c ) set(PUBLIC_HEADER @@ -15,15 +25,19 @@ set(PUBLIC_HEADER ) set(PUBLIC_DEFINES - SUPPORT_N706A_NET + SUPPORT_N706A_NET # 定义此宏启用 N706A AT 模式(独立于PPP) + SUPPORT_AT_N706A_NET # 开机自动注册 N706A AT 命令 + # SSL实现方式(二选一,仅影响n706a_net内部): + N706A_SSL_USE_AT_COMMAND # 使用AT指令实现SSL(推荐,利用N706A内置SSL) + # N706A_SSL_USE_MBEDTLS # 使用mbedtls库实现SSL(不推荐:与AT模式不兼容) ) set(PRIVATE_HEADER + ${CMAKE_CURRENT_SOURCE_DIR}/../ppp/include ) set(PRIVATE_DEFINES -# N706A_SSL_USE_MBEDTLS -N706A_SSL_USE_AT_COMMAND + # SSL宏已移至 PUBLIC_DEFINES,以便其他模块能检测到 ) # use this when you want to add ccflags like -include xxx @@ -44,6 +58,11 @@ set(MAIN_COMPONENT false ) +# 添加依赖:n706a_net 需要链接 ppp 组件的部分函数 +set(DEPENDENCIES + ppp +) + build_component() install_sdk(${CMAKE_CURRENT_SOURCE_DIR} "*") diff --git a/code/sdk/zhihui/n706a_net/example/n706a_net_at_cmd.c b/code/sdk/zhihui/n706a_net/example/n706a_net_at_cmd.c new file mode 100644 index 000000000..d3e2458ad --- /dev/null +++ b/code/sdk/zhihui/n706a_net/example/n706a_net_at_cmd.c @@ -0,0 +1,475 @@ +/** + * @file n706a_net_at_cmd.c + * @brief N706A AT网络的AT命令接口(参考ppp_at_cmd.c实现) + */ + +#include "n706a_net_at_cmd.h" +#include "n706a_net_example.h" +#include "n706a_net_api.h" +#include "n706a_at_handler.h" +#include "ppp_debug.h" +#include "ppp_interrupt.h" +#include "at_cmd_api.h" +#include "osal_debug.h" +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* 工具宏定义 */ +#define ARRAY_COUNT(array) (sizeof(array) / sizeof(array[0])) +#define CUSTOM_UTILS_MAX_ARGC 8 +#define CUSTOM_UTILS_MAX_ARGV_LEN 256 +#define CUSTOM_UTILS_SUCCESS 0 +#define CUSTOM_UTILS_FAILED (-1) + +/** + * @brief 参数解析工具函数 + */ +static int ConvertCmdToParam(uint8_t *para, uint32_t paraLen, + uint8_t argv[CUSTOM_UTILS_MAX_ARGC][CUSTOM_UTILS_MAX_ARGV_LEN], uint32_t* argc) +{ + uint32_t pos = 0; + *argc = 0; + + for (uint32_t i = 0; i < paraLen; i++) { + if ((para[i] == ' ' || para[i] == ',' || para[i] == '\n') && pos != 0) { + argv[*argc][pos] = '\0'; + *argc += 1; + pos = 0; + if (*argc >= CUSTOM_UTILS_MAX_ARGC) { + return CUSTOM_UTILS_FAILED; + } + } else { + argv[*argc][pos] = para[i]; + pos++; + if (pos >= CUSTOM_UTILS_MAX_ARGV_LEN) { + return CUSTOM_UTILS_FAILED; + } + } + } + if (pos != 0) { + *argc += 1; + } + + return CUSTOM_UTILS_SUCCESS; +} + +/** + * @brief AT^N706ANETINIT - 初始化N706A AT网络环境 + * + * 用法: AT^N706ANETINIT + * + * 该命令会初始化: + * - 4G模块电源控制 + * - UART串口(2M波特率) + * - AT命令处理器(异步接收) + * - 中断接收系统(Ping-Pong缓冲) + */ +static errcode_t at_n706a_net_env_init(uint8_t *para, uint32_t para_len, char *strCommonCmd) +{ + UNUSED(para); + UNUSED(para_len); + UNUSED(strCommonCmd); + + PPP_DEBUG_INFO("=== N706A AT Network Initialization ===\n"); + + int ret = n706a_net_env_init(); + if (ret != 0) { + PPP_DEBUG_ERROR("N706A network initialization failed: %d\n", ret); + return ERRCODE_FAIL; + } + + PPP_DEBUG_INFO("SUCCESS: N706A AT network initialized\n"); + PPP_DEBUG_INFO(" - You can now use HTTP/WebSocket functions\n"); + PPP_DEBUG_INFO(" - Example: Use zhihui_net APIs for networking\n"); + + return ERRCODE_SUCC; +} + +/** + * @brief AT^N706ANETDEINIT - 反初始化N706A AT网络环境 + * + * 用法: AT^N706ANETDEINIT + */ +static errcode_t at_n706a_net_env_deinit(uint8_t *para, uint32_t para_len, char *strCommonCmd) +{ + UNUSED(para); + UNUSED(para_len); + UNUSED(strCommonCmd); + + PPP_DEBUG_INFO("=== N706A AT Network Deinitialization ===\n"); + + int ret = n706a_net_env_deinit(); + if (ret != 0) { + PPP_DEBUG_ERROR("N706A network deinitialization failed: %d\n", ret); + return ERRCODE_FAIL; + } + + PPP_DEBUG_INFO("SUCCESS: N706A AT network deinitialized\n"); + + return ERRCODE_SUCC; +} + +/** + * @brief AT^N706ANETSTATUS - 查询N706A AT网络状态 + * + * 用法: AT^N706ANETSTATUS + */ +static errcode_t at_n706a_net_status(uint8_t *para, uint32_t para_len, char *strCommonCmd) +{ + UNUSED(para); + UNUSED(para_len); + UNUSED(strCommonCmd); + + PPP_DEBUG_INFO("=== N706A AT Network Status ===\n"); + + bool initialized = is_n706a_env_initialized(); + uint32_t baudrate = get_n706a_baudrate(); + + PPP_DEBUG_INFO("Initialized: %s\n", initialized ? "YES" : "NO"); + PPP_DEBUG_INFO("Baudrate: %u\n", baudrate); + PPP_DEBUG_INFO("Mode: Pure AT commands (no PPP)\n"); + PPP_DEBUG_INFO("Reception: Asynchronous interrupt\n"); + + // 打印中断统计信息 + ppp_interrupt_print_stats(); + + return ERRCODE_SUCC; +} + +/** + * @brief AT^N706ANETBAUD - 设置N706A波特率 + * + * 用法: AT^N706ANETBAUD= + * 参数: baudrate - 波特率(建议2000000或1000000) + * + * 示例: AT^N706ANETBAUD=2000000 + */ +static errcode_t at_n706a_net_set_baudrate(uint8_t *para, uint32_t para_len, char *strCommonCmd) +{ + UNUSED(strCommonCmd); + + if (para == NULL || para_len == 0) { + PPP_DEBUG_ERROR("Missing baudrate parameter\n"); + PPP_DEBUG_INFO("Usage: AT^N706ANETBAUD=\n"); + PPP_DEBUG_INFO("Example: AT^N706ANETBAUD=2000000\n"); + return ERRCODE_FAIL; + } + + uint8_t argv[CUSTOM_UTILS_MAX_ARGC][CUSTOM_UTILS_MAX_ARGV_LEN] = { 0 }; + uint32_t argc = 0; + + int ret = ConvertCmdToParam(para, para_len, argv, &argc); + if (ret != CUSTOM_UTILS_SUCCESS || argc != 1) { + PPP_DEBUG_ERROR("Invalid parameter format\n"); + return ERRCODE_FAIL; + } + + uint32_t baudrate = atoi((char*)argv[0]); + + if (baudrate != 115200 && baudrate != 1000000 && baudrate != 2000000) { + PPP_DEBUG_WARN("Unusual baudrate value: %u\n", baudrate); + PPP_DEBUG_INFO("Recommended: 2000000, 1000000, or 115200\n"); + } + + set_n706a_baudrate(baudrate); + PPP_DEBUG_INFO("Baudrate set to: %u\n", baudrate); + PPP_DEBUG_INFO("Note: This will take effect on next initialization\n"); + + return ERRCODE_SUCC; +} + +/** + * @brief AT^N706ANETTEST - 测试AT命令通信 + * + * 用法: AT^N706ANETTEST + */ +static errcode_t at_n706a_net_test_communication(uint8_t *para, uint32_t para_len, char *strCommonCmd) +{ + UNUSED(para); + UNUSED(para_len); + UNUSED(strCommonCmd); + + PPP_DEBUG_INFO("=== N706A AT Communication Test ===\n"); + + if (!is_n706a_env_initialized()) { + PPP_DEBUG_ERROR("N706A network not initialized\n"); + PPP_DEBUG_INFO("Please run AT^N706ANETINIT first\n"); + return ERRCODE_FAIL; + } + + n706a_at_error_t ret = n706a_at_test_communication(); + if (ret == N706A_AT_OK) { + PPP_DEBUG_INFO("AT communication test PASSED\n"); + return ERRCODE_SUCC; + } else { + PPP_DEBUG_ERROR("AT communication test FAILED: %d\n", ret); + return ERRCODE_FAIL; + } +} + +/** + * @brief AT^N706ANETCMD - 发送原始AT命令 + * + * 用法: AT^N706ANETCMD= + * 示例: AT^N706ANETCMD=AT+CSQ + */ +static errcode_t at_n706a_net_send_at_command(uint8_t *para, uint32_t para_len, char *strCommonCmd) +{ + UNUSED(strCommonCmd); + + if (para == NULL || para_len == 0) { + PPP_DEBUG_ERROR("Missing AT command parameter\n"); + PPP_DEBUG_INFO("Usage: AT^N706ANETCMD=\n"); + PPP_DEBUG_INFO("Example: AT^N706ANETCMD=AT+CSQ\n"); + return ERRCODE_FAIL; + } + + if (!is_n706a_env_initialized()) { + PPP_DEBUG_ERROR("N706A network not initialized\n"); + PPP_DEBUG_INFO("Please run AT^N706ANETINIT first\n"); + return ERRCODE_FAIL; + } + + // 构建AT命令字符串 + char at_cmd[256]; + if (para_len >= sizeof(at_cmd)) { + PPP_DEBUG_ERROR("AT command too long (max %d chars)\n", sizeof(at_cmd) - 1); + return ERRCODE_FAIL; + } + + memcpy(at_cmd, para, para_len); + at_cmd[para_len] = '\0'; + + PPP_DEBUG_CRITICAL("Sending AT command: [%s]", at_cmd); + + // 发送AT命令 + n706a_at_response_t response; + const uint32_t timeout_ms = 5000; + + n706a_at_error_t ret = n706a_at_send_command(at_cmd, NULL, timeout_ms, &response); + + if (ret == N706A_AT_OK && response.success) { + if (response.length > 0) { + // 强制打印响应(使用 CRITICAL 级别确保可见) + PPP_DEBUG_CRITICAL("=== AT Response (%u bytes) ===", response.length); + PPP_DEBUG_CRITICAL("%s", response.response); + PPP_DEBUG_CRITICAL("=== End AT Response ==="); + } else { + PPP_DEBUG_WARN("Command executed successfully but no response data received"); + } + return ERRCODE_SUCC; + } else { + PPP_DEBUG_ERROR("AT command failed: error=%d, success=%d", ret, response.success); + return ERRCODE_FAIL; + } +} + +/** + * @brief AT^N706ANETSIGNAL - 获取信号强度 + * + * 用法: AT^N706ANETSIGNAL + */ +static errcode_t at_n706a_net_get_signal(uint8_t *para, uint32_t para_len, char *strCommonCmd) +{ + UNUSED(para); + UNUSED(para_len); + UNUSED(strCommonCmd); + + PPP_DEBUG_INFO("=== N706A Signal Information ===\n"); + + if (!is_n706a_env_initialized()) { + PPP_DEBUG_ERROR("N706A network not initialized\n"); + return ERRCODE_FAIL; + } + + n706a_network_signal_info_t signal_info; + n706a_at_error_t ret = n706a_at_get_network_signal_info(&signal_info); + + if (ret == N706A_AT_OK && signal_info.valid) { + PPP_DEBUG_INFO("Signal Information:\n"); + PPP_DEBUG_INFO(" RXLEV: %d (GSM signal level, 0-63, 99=invalid)\n", signal_info.rxlev); + PPP_DEBUG_INFO(" BER: %d (Bit error rate, 0-7, 99=invalid)\n", signal_info.ber); + PPP_DEBUG_INFO(" RSCP: %d (UMTS, 0-96, 255=invalid)\n", signal_info.rscp); + PPP_DEBUG_INFO(" ECNO: %d (UMTS, 0-49, 255=invalid)\n", signal_info.ecno); + PPP_DEBUG_INFO(" RSRQ: %d (LTE quality, 0-49, 255=invalid)\n", signal_info.rsrq); + PPP_DEBUG_INFO(" RSRP: %d (LTE power, 0-97, 255=invalid)\n", signal_info.rsrp); + return ERRCODE_SUCC; + } else { + PPP_DEBUG_ERROR("Failed to get signal information: %d\n", ret); + return ERRCODE_FAIL; + } +} + +/** + * @brief AT^N706ANETDEBUG - 设置调试级别 + * + * 用法: AT^N706ANETDEBUG= + * level: 0=NONE, 1=ERROR, 2=WARN, 3=INFO, 4=DEBUG + */ +static errcode_t at_n706a_net_set_debug_level(uint8_t *para, uint32_t para_len, char *strCommonCmd) +{ + UNUSED(strCommonCmd); + + if (!para || para_len == 0) { + PPP_DEBUG_ERROR("Missing debug level parameter\n"); + PPP_DEBUG_INFO("Usage: AT^N706ANETDEBUG=\n"); + PPP_DEBUG_INFO("Levels: 0=NONE, 1=ERROR, 2=WARN, 3=INFO, 4=DEBUG\n"); + return ERRCODE_FAIL; + } + + uint8_t argv[CUSTOM_UTILS_MAX_ARGC][CUSTOM_UTILS_MAX_ARGV_LEN] = { 0 }; + uint32_t argc = 0; + + int ret = ConvertCmdToParam(para, para_len, argv, &argc); + if (ret != CUSTOM_UTILS_SUCCESS || argc != 1) { + PPP_DEBUG_ERROR("Invalid parameter format\n"); + return ERRCODE_FAIL; + } + + int level = atoi((char*)argv[0]); + + if (level < 0 || level > 4) { + PPP_DEBUG_ERROR("Invalid debug level %d (must be 0-4)\n", level); + return ERRCODE_FAIL; + } + + ppp_error_t ppp_ret = ppp_debug_set_level(level); + if (ppp_ret == PPP_ERR_OK) { + PPP_DEBUG_INFO("Debug level set to %d (%s)\n", level, ppp_debug_get_level_name(level)); + return ERRCODE_SUCC; + } else { + PPP_DEBUG_ERROR("Failed to set debug level\n"); + return ERRCODE_FAIL; + } +} + +/** + * @brief AT^N706ASETBAUD - 设置N706A波特率到配置模型(对应PPP的AT^PPPSETBAUD) + * @param para 参数: 波特率值 + * @param para_len 参数长度 + * + * 用法: AT^N706ASETBAUD= + * 示例: AT^N706ASETBAUD=115200 + * + * 支持的波特率: 9600, 19200, 38400, 57600, 115200, 230400, 460800, 921600, 1000000, 2000000 + */ +static errcode_t at_n706a_set_config_baudrate(uint8_t *para, uint32_t para_len, char *strCommonCmd) +{ + UNUSED(strCommonCmd); + + if (!para || para_len == 0) { + PPP_DEBUG_ERROR("N706A SET BAUDRATE: Missing baudrate parameter\n"); + return ERRCODE_FAIL; + } + + uint8_t argv[CUSTOM_UTILS_MAX_ARGC][CUSTOM_UTILS_MAX_ARGV_LEN] = { 0 }; + uint32_t argc = 0; + + int ret = ConvertCmdToParam(para, para_len, argv, &argc); + if (ret != CUSTOM_UTILS_SUCCESS || argc != 1) { + PPP_DEBUG_ERROR("N706A SET BAUDRATE: Invalid parameter format, usage: AT^N706ASETBAUD=baudrate\n"); + return ERRCODE_FAIL; + } + + uint32_t baudrate = atoi((char*)argv[0]); + + // 检查波特率是否为支持的值 + if (baudrate != 9600 && baudrate != 19200 && baudrate != 38400 && + baudrate != 57600 && baudrate != 115200 && baudrate != 230400 && + baudrate != 460800 && baudrate != 921600 && baudrate != 1000000 && baudrate != 2000000) { + PPP_DEBUG_ERROR("N706A SET BAUDRATE: Invalid baudrate %d\n", baudrate); + PPP_DEBUG_ERROR("N706A SET BAUDRATE: Supported values: 9600,19200,38400,57600,115200,230400,460800,921600,1000000,2000000\n"); + return ERRCODE_FAIL; + } + + // 获取当前配置的波特率 + uint32_t current_baudrate = get_n706a_baudrate(); + + if (current_baudrate == baudrate) { + PPP_DEBUG_INFO("N706A SET BAUDRATE: Baudrate already set to %d, no change needed\n", baudrate); + // return ERRCODE_SUCC; + } + + // 设置新的波特率到配置模型 + set_n706a_baudrate(baudrate); + + PPP_DEBUG_INFO("N706A SET BAUDRATE: Config model updated: %d -> %d\n", current_baudrate, baudrate); + + // 尝试动态更新 N706A模块 波特率(发送 AT+IPR 命令) + PPP_DEBUG_INFO("N706A SET BAUDRATE: Attempting to set N706A module baudrate to %d...\n", baudrate); + errcode_t rett = n706a_api_set_baudrate_in_all_mode(baudrate); + + if (rett == ERRCODE_SUCC) { + PPP_DEBUG_INFO("N706A SET BAUDRATE: Successfully set N706A module baudrate to %d\n", baudrate); + PPP_DEBUG_INFO("N706A SET BAUDRATE: Baudrate change completed successfully\n"); + } else { + PPP_DEBUG_ERROR("N706A SET BAUDRATE: Failed to set N706A module baudrate (ret: %d)\n", ret); + PPP_DEBUG_ERROR("N706A SET BAUDRATE: Config model updated, but N706A module baudrate NOT changed\n"); + PPP_DEBUG_ERROR("N706A SET BAUDRATE: Please restart N706A module to apply the new baudrate\n"); + // 虽然模块设置失败,但配置已更新,下次重启会生效 + // 根据需求可以选择返回 ERRCODE_SUCC 或 ERRCODE_FAIL + return ERRCODE_FAIL; // 返回失败,让用户知道当前操作未完全成功 + } + + return ERRCODE_SUCC; +} + +/** + * @brief AT^N706AGETBAUD - 获取N706A波特率配置(对应PPP的AT^PPPGETBAUD) + * + * 用法: AT^N706AGETBAUD + */ +static errcode_t at_n706a_get_config_baudrate(uint8_t *para, uint32_t para_len, char *strCommonCmd) +{ + UNUSED(para); + UNUSED(para_len); + UNUSED(strCommonCmd); + + uint32_t config_baudrate = get_n706a_baudrate(); + + PPP_DEBUG_INFO("N706A GET BAUDRATE: Config model baudrate: %d\n", config_baudrate); + PPP_DEBUG_INFO("N706A GET BAUDRATE: This is the baudrate that will be used for N706A communication\n"); + + if (is_n706a_env_initialized()) { + PPP_DEBUG_INFO("N706A GET BAUDRATE: N706A is currently initialized with this baudrate\n"); + } else { + PPP_DEBUG_INFO("N706A GET BAUDRATE: N706A is not initialized yet\n"); + } + + return ERRCODE_SUCC; +} + +/* N706A AT网络命令表 */ +static const at_cmd_table_t g_n706a_net_at_cmd_table[] = { + { (char *)"AT^N706ANETINIT", at_n706a_net_env_init, (char *)" " }, + { (char *)"AT^N706ANETDEINIT", at_n706a_net_env_deinit, (char *)" " }, + { (char *)"AT^N706ANETSTATUS", at_n706a_net_status, (char *)" " }, + { (char *)"AT^N706ANETBAUD", at_n706a_net_set_baudrate, (char *)"=" }, + { (char *)"AT^N706ANETTEST", at_n706a_net_test_communication, (char *)" " }, + { (char *)"AT^N706ANETCMD", at_n706a_net_send_at_command, (char *)"=" }, + { (char *)"AT^N706ANETSIGNAL", at_n706a_net_get_signal, (char *)" " }, + { (char *)"AT^N706ANETDEBUG", at_n706a_net_set_debug_level, (char *)"=" }, + { (char *)"AT^N706ASETBAUD", at_n706a_set_config_baudrate, (char *)"=" }, + { (char *)"AT^N706AGETBAUD", at_n706a_get_config_baudrate, (char *)" " }, +}; + +/** + * @brief 获取N706A AT网络命令表 + * + * @param cmd_count 输出参数,返回命令数量 + * @return 命令表指针 + */ +at_cmd_table_t *get_n706a_net_cmd_table(uint32_t *cmd_count) +{ + *cmd_count = sizeof(g_n706a_net_at_cmd_table) / sizeof(at_cmd_table_t); + return (at_cmd_table_t *)g_n706a_net_at_cmd_table; +} + +#ifdef __cplusplus +} +#endif diff --git a/code/sdk/zhihui/n706a_net/example/n706a_net_example.c b/code/sdk/zhihui/n706a_net/example/n706a_net_example.c new file mode 100644 index 000000000..e48c1310d --- /dev/null +++ b/code/sdk/zhihui/n706a_net/example/n706a_net_example.c @@ -0,0 +1,171 @@ +/** + * @file n706a_net_example.c + * @brief N706A AT网络使用示例 + */ + +#include "n706a_net_example.h" +#include "n706a_net_api.h" +#include "ppp_config.h" +#include "ppp_debug.h" +#include "osal_debug.h" +#include "schedule/osal_task.h" +#include "ConfigModelC.h" // 用于ConfigModel波特率持久化 +#include + +/* 使用 PPP_DEBUG_INFO 替代 PRINT */ +#ifndef PRINT +#define PRINT(fmt, ...) PPP_DEBUG_INFO(fmt, ##__VA_ARGS__) +#endif + +static bool g_n706a_env_initialized = false; +static uint32_t g_n706a_baudrate = PPP_UART_BAUDRATE; + +/** + * @brief N706A网络环境初始化 + * @return 0成功,-1失败 + */ +int n706a_net_env_init(void) +{ + if (g_n706a_env_initialized) { + PRINT("N706A network environment already initialized\r\n"); + return 0; + } + + PRINT("N706A Network Environment Initialization Started\r\n"); + + /* 1. 配置N706A网络参数 */ + n706a_net_config_t config = { + .baudrate = g_n706a_baudrate, // 波特率(2M或1M) + .auto_baudrate = true, // 启用自动波特率检测 + .init_timeout = 30000 // 30秒初始化超时 + }; + + /* 2. 初始化N706A AT网络系统 */ + PRINT("Initializing N706A AT network system...\r\n"); + errcode_t ret = n706a_net_init(&config); + if (ret != ERRCODE_SUCC) { + PRINT("N706A network init failed: error code %d\r\n", ret); + return -1; + } + + g_n706a_env_initialized = true; + PRINT("N706A network environment initialized successfully (Pure AT mode)\r\n"); + PRINT(" - Mode: AT commands only (no PPP dialing)\r\n"); + PRINT(" - Reception: Asynchronous interrupt with ring buffer\r\n"); + PRINT(" - Baudrate: %u\r\n", g_n706a_baudrate); + + if (n706a_net_is_power_initialized()) { + PRINT("4G power control is enabled\r\n"); + } else { + PRINT("4G power control is disabled\r\n"); + } + + /* 等待5秒让调制解调器完全稳定 */ + PRINT("Waiting for modem stabilization...\r\n"); + osal_msleep(5000); + + /* 更新所有运行时状态(IMEI、信号、运营商等) */ + PRINT("Updating runtime status...\r\n"); + ret = n706a_api_update_all_runtime_status(); + if (ret != ERRCODE_SUCC) { + PRINT("Warning: Failed to update runtime status\r\n"); + } + + /* 获取并显示网络信号信息 */ + PRINT("Getting network signal info...\r\n"); + ret = n706a_get_network_signal_info(); + if (ret != ERRCODE_SUCC) { + PRINT("Warning: Failed to get network signal info\r\n"); + } + + #if 0 + /* 启用省电模式 */ + PRINT("Enabling power save mode...\r\n"); + ret = n706a_enable_power_save_mode(); + if (ret != ERRCODE_SUCC) { + PRINT("Warning: Failed to enable power save mode\r\n"); + } + #endif + + PRINT("N706A network ready for use\r\n"); + + /* 启动 WebSocket 服务 */ + extern errcode_t uapi_ln_ws_start(void); + PRINT("Starting WebSocket service...\r\n"); + ret = uapi_ln_ws_start(); + if (ret != ERRCODE_SUCC) { + PRINT("Warning: Failed to start WebSocket service: %d\r\n", ret); + } else { + PRINT("WebSocket service started successfully\r\n"); + } + + return 0; +} + +/** + * @brief 获取N706A波特率 + * + * @note N706A和PPP是互斥的,复用PPP的ConfigModel配置项 + */ +uint32_t get_n706a_baudrate(void) +{ + // 从ConfigModel读取(与PPP共用配置项,因为它们互斥) + uint32_t baudrate = get_ppp_baudrate_from_config_model(); + + // 如果ConfigModel中没有配置,返回默认值 + if (baudrate == 0) { + baudrate = g_n706a_baudrate; // 使用默认值 + } + + return baudrate; +} + +/** + * @brief 设置N706A波特率 + * + * @note N706A和PPP是互斥的,复用PPP的ConfigModel配置项实现持久化 + */ +void set_n706a_baudrate(uint32_t value) +{ + // 同时更新全局变量和ConfigModel(持久化保存) + g_n706a_baudrate = value; + + // 保存到ConfigModel(与PPP共用配置项,因为它们互斥) + set_ppp_baudrate_from_config_model(value); + + PPP_DEBUG_INFO("N706A baudrate saved to ConfigModel: %u", value); +} + +/** + * @brief 检查N706A网络环境是否已初始化 + */ +bool is_n706a_env_initialized(void) +{ + return g_n706a_env_initialized; +} + +/** + * @brief N706A网络环境反初始化 + */ +int n706a_net_env_deinit(void) +{ + if (!g_n706a_env_initialized) { + PRINT("N706A network environment not initialized\r\n"); + return 0; + } + + PRINT("N706A Network Environment Deinitialization Started\r\n"); + + /* 反初始化N706A网络系统 */ + errcode_t ret = n706a_net_deinit(); + if (ret != ERRCODE_SUCC) { + PRINT("N706A network deinit failed: error code %d\r\n", ret); + return -1; + } + + g_n706a_env_initialized = false; + PRINT("N706A network environment deinitialized successfully\r\n"); + + return 0; +} + diff --git a/code/sdk/zhihui/n706a_net/include/n706a_at_handler.h b/code/sdk/zhihui/n706a_net/include/n706a_at_handler.h new file mode 100644 index 000000000..431023d41 --- /dev/null +++ b/code/sdk/zhihui/n706a_net/include/n706a_at_handler.h @@ -0,0 +1,139 @@ +/** + * @file n706a_at_handler.h + * @brief N706A AT命令处理器(纯AT模式) + * + * 专用于n706a_net的AT指令处理,与PPP模式互斥 + * 提供异步串口接收保证高速数据完整性 + */ + +#ifndef N706A_AT_HANDLER_H +#define N706A_AT_HANDLER_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include + +/* AT响应最大长度 */ +#define N706A_AT_RESPONSE_MAX_LEN 1024 +#define N706A_AT_CMD_MAX_LEN 256 +#define N706A_AT_CMD_TIMEOUT 5000 // 默认5秒超时 + +/* 错误码定义 */ +typedef enum { + N706A_AT_OK = 0, + N706A_AT_ERR_INVALID_PARAM, + N706A_AT_ERR_MEMORY, + N706A_AT_ERR_UART, + N706A_AT_ERR_TIMEOUT, + N706A_AT_ERR_AT_FAILED, +} n706a_at_error_t; + +/** + * @brief AT响应结构体 + */ +typedef struct { + char response[N706A_AT_RESPONSE_MAX_LEN]; // 响应内容 + uint32_t length; // 响应长度 + bool success; // 是否成功 + n706a_at_error_t error_code; // 错误码 +} n706a_at_response_t; + +/** + * @brief 初始化AT命令处理模块 + * @param uart_bus UART总线号 + * @param baudrate 波特率 + * @return 错误码 + */ +n706a_at_error_t n706a_at_handler_init(uint8_t uart_bus, uint32_t baudrate); + +/** + * @brief 反初始化AT命令处理模块 + */ +void n706a_at_handler_deinit(void); + +/** + * @brief 发送AT命令并等待响应 + * @param command AT命令字符串(不含\r\n) + * @param expected_response 期望的响应字符串(如"OK"),NULL表示不检查 + * @param timeout 超时时间(ms) + * @param response 响应结构体指针 + * @return 错误码 + */ +n706a_at_error_t n706a_at_send_command(const char *command, + const char *expected_response, + uint32_t timeout, + n706a_at_response_t *response); + +/** + * @brief 发送原始数据(不带AT命令格式) + * @param data 要发送的数据 + * @param len 数据长度 + * @param timeout 超时时间(ms) + * @return 错误码 + * @note 用于 TCP/SSL 数据发送,在 AT+TCPSEND/AT+SSLTCPSEND 命令后使用 + */ +n706a_at_error_t n706a_at_send_raw_data(const void *data, size_t len, uint32_t timeout); + +/** + * @brief 设置波特率 + * @param baudrate 波特率 + * @return 错误码 + */ +n706a_at_error_t n706a_at_set_baudrate(uint32_t baudrate); + +/** + * @brief 获取当前波特率 + * @return 当前波特率 + */ +uint32_t n706a_at_get_current_baudrate(void); + +/** + * @brief 测试AT通信是否正常 + * @return 错误码 + */ +n706a_at_error_t n706a_at_test_communication(void); + +/** + * @brief 网络信号信息结构体 (基于AT+CESQ命令) + */ +typedef struct { + int16_t rxlev; // GSM信号接收功率 (0-63, 99=无效) + int16_t ber; // 误码率 (0-7, 99=无效) + int16_t rscp; // UMTS下行载波干扰比率 (0-96, 255=无效) + int16_t ecno; // UMTS下行载波干扰比率 (0-49, 255=无效) + int16_t rsrq; // LTE参考信号质量 (0-49, 255=无效) + int16_t rsrp; // LTE参考信号接收功率 (0-97, 255=无效) + bool valid; // 数据是否有效 +} n706a_network_signal_info_t; + +/** + * @brief 获取网络信号信息 + * @param info 信号信息结构体指针 + * @return 错误码 + */ +n706a_at_error_t n706a_at_get_network_signal_info(n706a_network_signal_info_t *info); + +/** + * @brief TCP URC数据接收回调函数类型 + * @param socket_id Socket ID (0-5) + * @param data 接收到的TCP数据 + * @param length 数据长度 + */ +typedef void (*n706a_tcp_urc_callback_t)(int socket_id, const uint8_t *data, size_t length); + +/** + * @brief 注册TCP URC接收回调 + * @param callback 回调函数指针,NULL表示取消注册 + */ +void n706a_at_register_tcp_urc_callback(n706a_tcp_urc_callback_t callback); + +#ifdef __cplusplus +} +#endif + +#endif /* N706A_AT_HANDLER_H */ + diff --git a/code/sdk/zhihui/n706a_net/include/n706a_http_client.h b/code/sdk/zhihui/n706a_net/include/n706a_http_client.h index fe6b150ac..5e6a91852 100644 --- a/code/sdk/zhihui/n706a_net/include/n706a_http_client.h +++ b/code/sdk/zhihui/n706a_net/include/n706a_http_client.h @@ -53,6 +53,7 @@ typedef enum { N706A_HTTP_EPROTO = -10, /**< 协议错误 */ N706A_HTTP_EUNKOWN = -11, /**< 未知错误 */ N706A_HTTP_ETIMEOUT = -12, /**< 超时 */ + N706A_HTTP_ENOMEM = -13, /**< 内存分配失败 */ } n706a_httpc_result_t; /** diff --git a/code/sdk/zhihui/n706a_net/include/n706a_net_api.h b/code/sdk/zhihui/n706a_net/include/n706a_net_api.h new file mode 100644 index 000000000..3ea7d89d2 --- /dev/null +++ b/code/sdk/zhihui/n706a_net/include/n706a_net_api.h @@ -0,0 +1,130 @@ +/** + * @file n706a_net_api.h + * @brief N706A AT命令网络API接口(独立于PPP) + */ + +#ifndef N706A_NET_API_H +#define N706A_NET_API_H + +#include +#include +#include "errcode.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief N706A网络状态枚举 + */ +typedef enum { + N706A_NET_STATE_IDLE = 0, // 空闲状态 + N706A_NET_STATE_READY, // 就绪状态(已初始化) + N706A_NET_STATE_ERROR // 错误状态 +} n706a_net_state_t; + +/** + * @brief N706A网络配置参数 + */ +typedef struct { + uint32_t baudrate; // 波特率(建议2000000或1000000) + bool auto_baudrate; // 是否启用自动波特率检测 + uint32_t init_timeout; // 初始化超时时间(毫秒) +} n706a_net_config_t; + +/** + * @brief 初始化N706A AT网络系统 + * + * 该函数会完成以下初始化: + * 1. 4G模块电源控制 + * 2. UART串口初始化 + * 3. AT命令处理器初始化 + * 4. 异步中断接收系统初始化 + * + * @param config 网络配置参数 + * @return ERRCODE_SUCC 成功,其他值失败 + */ +errcode_t n706a_net_init(const n706a_net_config_t *config); + +/** + * @brief 反初始化N706A AT网络系统 + * + * @return ERRCODE_SUCC 成功,其他值失败 + */ +errcode_t n706a_net_deinit(void); + +/** + * @brief 初始化4G电源控制 + * + * @return ERRCODE_SUCC 成功,其他值失败 + */ +errcode_t n706a_net_power_init(void); + +/** + * @brief 控制4G电源开关 + * + * @param is_open true打开,false关闭 + * @return ERRCODE_SUCC 成功,其他值失败 + */ +errcode_t n706a_net_power_control(bool is_open); + +/** + * @brief 检查4G电源是否已初始化 + * + * @return true已初始化,false未初始化 + */ +bool n706a_net_is_power_initialized(void); + +/** + * @brief 获取N706A网络状态 + * + * @return n706a_net_state_t 当前状态 + */ +n706a_net_state_t n706a_net_get_state(void); + +/** + * @brief 获取并显示网络信号信息 + * + * @return ERRCODE_SUCC 成功,其他值失败 + */ +errcode_t n706a_get_network_signal_info(void); + +/** + * @brief 启用4G模块电源省电模式 + * + * @return ERRCODE_SUCC 成功,其他值失败 + * + * 功能说明: + * - 执行 AT+NWENPWRSAVE=1 命令 + * - 配置为:低电平唤醒,高电平进入休眠 + */ +errcode_t n706a_enable_power_save_mode(void); + +/** + * @brief 更新所有runtime变量值 + * + * @return ERRCODE_SUCC 成功,其他值失败 + * + * 包括:IMEI、信号强度、运营商、ICCID、SIM卡状态、网络状态、版本、时间等 + */ +errcode_t n706a_api_update_all_runtime_status(void); + +/** + * @brief 动态设置N706A模块波特率(在所有模式下) + * + * @param baudrate 目标波特率 + * @return ERRCODE_SUCC 成功,其他值失败 + * + * 功能说明: + * - 发送 AT+IPR 命令设置N706A模块波特率 + * - 同步更新MCU UART配置 + * - 如果设置失败会进行重试 + */ +errcode_t n706a_api_set_baudrate_in_all_mode(uint32_t baudrate); + +#ifdef __cplusplus +} +#endif + +#endif /* N706A_NET_API_H */ + diff --git a/code/sdk/zhihui/n706a_net/include/n706a_net_at_cmd.h b/code/sdk/zhihui/n706a_net/include/n706a_net_at_cmd.h new file mode 100644 index 000000000..f21153ea7 --- /dev/null +++ b/code/sdk/zhihui/n706a_net/include/n706a_net_at_cmd.h @@ -0,0 +1,41 @@ +/** + * @file n706a_net_at_cmd.h + * @brief N706A AT网络的AT命令接口 + */ + +#ifndef N706A_NET_AT_CMD_H +#define N706A_NET_AT_CMD_H + +#include +#include "errcode.h" +#include "at_cmd_api.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief 获取N706A AT网络命令表 + * + * 该函数返回N706A_NET的AT命令表,包含以下命令: + * - AT^N706ANETINIT : 初始化N706A AT网络 + * - AT^N706ANETDEINIT : 反初始化N706A AT网络 + * - AT^N706ANETSTATUS : 查询网络状态(含中断统计) + * - AT^N706ANETBAUD= : 设置波特率 + * - AT^N706ANETTEST : 测试AT通信 + * - AT^N706ANETCMD= : 发送原始AT命令 + * - AT^N706ANETSIGNAL : 获取信号强度 + * - AT^N706ANETDEBUG= : 设置调试级别 + * + * @param cmd_count 输出参数,返回命令数量 + * @return 命令表指针 + * + * @note 该函数通常由系统启动代码调用,自动注册所有命令 + */ +at_cmd_table_t *get_n706a_net_cmd_table(uint32_t *cmd_count); + +#ifdef __cplusplus +} +#endif + +#endif /* N706A_NET_AT_CMD_H */ diff --git a/code/sdk/zhihui/n706a_net/include/n706a_net_example.h b/code/sdk/zhihui/n706a_net/include/n706a_net_example.h new file mode 100644 index 000000000..87ef49b19 --- /dev/null +++ b/code/sdk/zhihui/n706a_net/include/n706a_net_example.h @@ -0,0 +1,62 @@ +/** + * @file n706a_net_example.h + * @brief N706A AT网络使用示例接口 + */ + +#ifndef N706A_NET_EXAMPLE_H +#define N706A_NET_EXAMPLE_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief N706A网络环境初始化 + * + * 该函数会完成所有必要的初始化工作: + * - 4G模块电源控制 + * - UART串口初始化 + * - AT命令处理器初始化 + * - 异步中断接收系统初始化 + * + * @return 0成功,-1失败 + */ +int n706a_net_env_init(void); + +/** + * @brief N706A网络环境反初始化 + * + * @return 0成功,-1失败 + */ +int n706a_net_env_deinit(void); + +/** + * @brief 获取N706A波特率 + * + * @return 当前波特率 + */ +uint32_t get_n706a_baudrate(void); + +/** + * @brief 设置N706A波特率 + * + * @param value 波特率值(建议2000000或1000000) + */ +void set_n706a_baudrate(uint32_t value); + +/** + * @brief 检查N706A网络环境是否已初始化 + * + * @return true已初始化,false未初始化 + */ +bool is_n706a_env_initialized(void); + +#ifdef __cplusplus +} +#endif + +#endif /* N706A_NET_EXAMPLE_H */ + diff --git a/code/sdk/zhihui/n706a_net/include/n706a_tcp_client.h b/code/sdk/zhihui/n706a_net/include/n706a_tcp_client.h index 227cacefb..8687c1404 100644 --- a/code/sdk/zhihui/n706a_net/include/n706a_tcp_client.h +++ b/code/sdk/zhihui/n706a_net/include/n706a_tcp_client.h @@ -74,6 +74,12 @@ typedef struct { bool is_ssl; /**< 是否SSL连接 */ uint32_t timeout_ms; /**< 超时时间 */ + /* URC接收缓存(用于存储 +TCPRECV URC 收到的数据) */ + uint8_t *urc_recv_buffer; /**< URC接收缓冲区 */ + size_t urc_recv_capacity; /**< 缓冲区容量 */ + size_t urc_recv_length; /**< 当前数据长度 */ + void *urc_recv_mutex; /**< 缓冲区互斥锁 */ + /* SSL相关字段(根据编译宏不同而不同) */ #ifdef N706A_SSL_USE_AT_COMMAND /* AT指令模式:SSL由模组处理 */ @@ -194,6 +200,31 @@ bool n706a_tcp_is_connected(const n706a_tcp_socket_t *sock); */ n706a_tcp_state_t n706a_tcp_get_state(const n706a_tcp_socket_t *sock); +/** + * @brief 初始化TCP socket的URC接收缓存 + * @param sock TCP socket指针 + * @param buffer_size 缓冲区大小(建议2048字节) + * @return N706A_TCP_SUCCESS成功,其他值表示失败 + * @note 由 n706a_tcp_socket_create 自动调用,通常不需要手动调用 + */ +n706a_tcp_result_t n706a_tcp_init_urc_buffer(n706a_tcp_socket_t *sock, size_t buffer_size); + +/** + * @brief 释放TCP socket的URC接收缓存 + * @param sock TCP socket指针 + * @note 由 n706a_tcp_close 自动调用,通常不需要手动调用 + */ +void n706a_tcp_free_urc_buffer(n706a_tcp_socket_t *sock); + +/** + * @brief 全局TCP URC数据接收回调(由n706a_at_handler调用) + * @param socket_id Socket ID + * @param data TCP数据 + * @param length 数据长度 + * @note 此函数在中断/线程上下文中被调用,需要注意线程安全 + */ +void n706a_tcp_on_urc_received(int socket_id, const uint8_t *data, size_t length); + #ifdef __cplusplus } #endif diff --git a/code/sdk/zhihui/n706a_net/include/ppp_cat1_api.h b/code/sdk/zhihui/n706a_net/include/ppp_cat1_api.h new file mode 100644 index 000000000..43cdf4b0d --- /dev/null +++ b/code/sdk/zhihui/n706a_net/include/ppp_cat1_api.h @@ -0,0 +1,203 @@ +/* + * @brief PPP模块的cat1 API兼容接口声明 + * @description 提供与cat1Service相同的接口声明,让UI和其他服务无需修改 + * Copyright (c) CompanyNameMagicTag 2024-2024. All rights reserved. + */ + +#ifndef __PPP_CAT1_API_H +#define __PPP_CAT1_API_H + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +#include +#include +#include "errcode.h" +#include "platform_core.h" + +// 前向声明,避免重复定义 +typedef enum _4g_cmd CMD_4G_T; +typedef struct ctl_pin_4g_s ctl_pin_4g_t; +typedef struct time_ctl_funcs_s time_ctl_funcs_t; +typedef errcode_t (*f_cat1_msg_callback)(int msg, void *msg_buf, uint16_t msglen); + +/********************** 主要接口 **********************/ + + +/** + * @brief cat1 开关机设置, 输入 true 开机, false 关机 + */ +void cat1_api_enable_cat1(bool isopen); + + +/** + * @brief 获取cat1是否使能(开机)状态 + */ +bool cat1_api_is_cat1_enabled(void); + + + +/** + * @brief return cmd uart rx enable flag + */ +bool cat1_api_custom_is_cmd_rx_enabled(void); + +/********************** 运行时状态获取接口 **********************/ + +/** + * @brief 获取cat1电源开启状态 + * @return 1 开机,0 关机 + */ +uint8_t cat1_api_runtime_get_cat1_powerstat(void); + +/** + * @brief 获取cat1网络状态 + * @return 1 网络在线,0 断网 + */ +uint8_t cat1_api_runtime_get_cat1_networkstat(void); + +/** + * @brief 获取cat1信号强度 + * @return 1~31 或者 99,99代表无网络 + */ +int cat1_api_runtime_get_cat1_signal(void); + +/** + * @brief 获取sim卡状态 + * @return 1 有sim卡,0 无sim卡 + */ +int cat1_api_runtime_get_cat1_simstat(void); + +/** + * @brief 获取网络类型 + * @return 0 "无网络", 1 "GSM", 2 "3G", 4 "4G/LTE" + */ +uint8_t cat1_api_runtime_get_cat1_networktype(void); + +/** + * @brief 获取Cat1运营商信息 + * @return 返回运营商字符串 + */ +char* cat1_api_runtime_get_cat1_operator(void); + +/** + * @brief 获取Cat1 ICCID号 + * @return 返回iccid字符串 + */ +char* cat1_api_runtime_get_cat1_iccid(void); + +/** + * @brief 获取Cat1 IMEI号 + * @return 返回imei字符串 + */ +char* cat1_api_runtime_get_cat1_imei(void); + +/** + * @brief 获取Cat1主应用版本号 + * @return 返回主应用版本号 + */ +char* cat1_api_runtime_get_cat1_pyver(void); + +/** + * @brief 获取modem版本 + * @return modem版本字符串 + */ +char* cat1_api_runtime_get_cat1_modemver(void); + +/********************** 运行时状态设置接口 **********************/ + +/** + * @brief 设置cat1电源状态 + */ +void cat1_api_runtime_set_cat1_powerstat(uint8_t cat1_powerOn); + +/** + * @brief 设置cat1网络状态 + */ +void cat1_api_runtime_set_cat1_networkstat(uint8_t network_on); + +/** + * @brief 设置cat1信号强度 + */ +void cat1_api_runtime_set_cat1_signal(int signal); + +/** + * @brief 设置sim卡状态 + */ +void cat1_api_runtime_set_cat1_simstat(int simstat); + +/** + * @brief 设置网络类型 + */ +void cat1_api_runtime_set_cat1_networktype(uint8_t networktype); + +/** + * @brief 设置Cat1运营商信息 + */ +void cat1_api_runtime_set_cat1_operator(char* cat1_operator); + +/** + * @brief 设置Cat1 IMEI号 + */ +void cat1_api_runtime_set_cat1_imei(char* imei); + +/** + * @brief 设置Cat1 ICCID号 + */ +void cat1_api_runtime_set_cat1_iccid(char* iccid); + +/** + * @brief 设置Cat1主应用版本号 + */ +void cat1_api_runtime_set_cat1_pyver(char* pyver); + +/** + * @brief 设置modem版本 + */ +void cat1_api_runtime_set_cat1_modemver(char* modemver); + +/********************** 控制接口 **********************/ + +/** + * @brief cat1 唤醒设置, 输入 true 唤醒cat1 modem, false cat1 modem自动休眠 + */ +void cat1_api_wakeup_cat1(uint8_t wakeup); + +/** + * @brief 保持唤醒Cat1 + */ +void cat1_api_keep_wakeup(uint8_t keep_wakeup); + +/** + * @brief 注册控制4G的gpio值 + */ +errcode_t cat1_register_ctl_pin(ctl_pin_4g_t ctl_pin); + +/** + * @brief cat1唤醒蓝牙中断处理函数 + */ +void cat1_ring_isr_callback(pin_t pin, uintptr_t param); + +/********************** 其他接口 **********************/ + +/** + * @brief 发送指令给Cat1 + */ +errcode_t cat1_cmd_send(CMD_4G_T cmd, void *cmd_buf, uint16_t cmdlen); + +/** + * @brief 注册消息回调函数 + */ +errcode_t cat1_register_msg_callback(f_cat1_msg_callback func); + +/** + * @brief 注册时间控制函数 + */ +errcode_t cat1_register_time_ctl_functions(time_ctl_funcs_t *funcs); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __PPP_CAT1_API_H */ \ No newline at end of file diff --git a/code/sdk/zhihui/n706a_net/include/ppp_config.h b/code/sdk/zhihui/n706a_net/include/ppp_config.h new file mode 100644 index 000000000..2f9836193 --- /dev/null +++ b/code/sdk/zhihui/n706a_net/include/ppp_config.h @@ -0,0 +1,138 @@ +#ifndef PPP_CONFIG_H +#define PPP_CONFIG_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +/* PPP配置参数 - UART配置统一使用SIO (在lwipopts_default.h中定义) */ +#define PPP_LOW_POWER_MODE 1 + +/* SIO设备号配置 - 与SLIP区分开 */ +#define PPP_IFNUM 0 // PPP使用的SIO设备号,与SLIP_IFNUM(0)区分 + +/* UART引脚配置 - 对应SIO配置 */ +#ifndef SIO_UART_TX_PIN +#define SIO_UART_TX_PIN S_MGPIO19 // UART TX引脚 +#endif +#ifndef SIO_UART_RX_PIN +#define SIO_UART_RX_PIN S_MGPIO20 // UART RX引脚 +#endif +#ifndef SIO_UART_BUS +#define SIO_UART_BUS UART_BUS_0 // UART总线 +#endif +#ifndef SIO_UART_IRQ_NUM +#define SIO_UART_IRQ_NUM UART_0_IRQN // UART IRQ引脚 +#endif + +#define PPP_UART_BAUDRATE 1000000 + +/* 支持的波特率列表(自适应模式) */ +#define PPP_BAUDRATE_AUTO_LIST {115200,921600,1000000,2000000} +#define PPP_BAUDRATE_AUTO_COUNT 4 + +/* 支持的固定波特率列表 */ +#define PPP_BAUDRATE_FIXED_LIST {4800, 9600, 57600, 115200, 230400, 460800, 750000, 921600, 1000000, 2000000, 4000000} +#define PPP_BAUDRATE_FIXED_COUNT 11 + +/* AT命令配置 */ +#define PPP_AT_CMD_TIMEOUT 3000 // AT命令超时时间(ms) +#define PPP_AT_DEFAULT_TIMEOUT 5000 // AT默认超时时间(ms) +#define PPP_AT_CMD_MAX_LEN 256 // AT命令最大长度 +#define PPP_AT_RESPONSE_MAX_LEN 1024 // AT响应最大长度 +#define PPP_AT_RETRY_COUNT 3 // AT命令重试次数 + +/* PPP拨号配置 */ +#define PPP_DIAL_TIMEOUT 30000 // 拨号超时时间(ms) +#define PPP_DIAL_RETRY_COUNT 3 // 拨号重试次数 +#define PPP_KEEPALIVE_INTERVAL 30 // 保活间隔(秒) + +/* PPP接收模式配置 */ +#ifdef SUPPORT_PPP_SERVICE_POLLING +#define PPP_USE_INTERRUPT_MODE 0 // 1=中断模式(UART中断接收), 0=轮询模式(线程轮询) +#elif defined(SUPPORT_PPP_SERVICE_INTERRUPT) +#define PPP_USE_INTERRUPT_MODE 1 // 1=中断模式(UART中断接收), 0=轮询模式(线程轮询) +#endif +//测试串口读取能力,注意不要注释掉,只用1和0 开启关闭即可。否则可能影响编译 + +#if defined(SKIP_LN_PROTOCOL_TEST) +#define PPP_DO_SERIAL_TEST 1 // 1=进行串口读取能力测试, 0=不进行串口读取能力测试 +#else +#define PPP_DO_SERIAL_TEST 0 // 1=进行串口读取能力测试, 0=不进行串口读取能力测试 +#endif + + +/* PPP性能优化配置 - 解决接收阻塞问题 */ +#define PPP_LWIP_PBUF_POOL_SIZE 32 // 增加pbuf池大小,默认16太小 +#define PPP_LWIP_TCPIP_MSG_INPKT 64 // 增加输入消息数量,默认32太小 +#define PPP_LWIP_TCPIP_MBOX_SIZE 128 // 增加TCPIP邮箱大小,默认64太小 +#define PPP_LWIP_TCP_RECVMBOX_SIZE 256 // 增加TCP接收邮箱大小,默认128太小 +#define PPP_LWIP_TCP_WND_SIZE 65536 // 增加TCP接收窗口,默认32KB太小 + +/* 缓冲区大小配置 */ +#define PPP_RX_BUFFER_SIZE 2048 // 接收缓冲区大小 +#define PPP_TX_BUFFER_SIZE 2048 // 发送缓冲区大小 + +/* PPP数据接收线程配置 */ +#define PPP_RX_THREAD_STACK_SIZE 4096 // 接收线程栈大小 +#define PPP_RX_THREAD_PRIORITY osPriorityHigh // 接收线程优先级(提高以确保及时读取数据) +#define PPP_PROCESS_THREAD_PRIORITY osPriorityAboveNormal // 处理线程优先级 + +/* 调试配置 */ +#define PPP_DEBUG_ENABLE 1 // 启用调试输出 +// #define PPP_DEBUG_LEVEL PPP_DEBUG_LEVEL_DEBUG // 默认只显示警告和错误 (1=ERROR, 2=WARN, 3=INFO, 4=DEBUG) +// #define PPP_DEBUG_LEVEL PPP_DEBUG_LEVEL_INFO // 默认只显示警告和错误 (1=ERROR, 2=WARN, 3=INFO, 4=DEBUG) +#define PPP_DEBUG_LEVEL PPP_DEBUG_LEVEL_WARN // 默认只显示警告和错误 (1=ERROR, 2=WARN, 3=INFO, 4=DEBUG) + +/* + * 调试级别说明: + * PPP_DEBUG_LEVEL_ERROR (1) - 只显示错误信息 + * PPP_DEBUG_LEVEL_WARN (2) - 显示警告和错误信息 + * PPP_DEBUG_LEVEL_INFO (3) - 显示信息、警告和错误 + * PPP_DEBUG_LEVEL_DEBUG (4) - 显示所有调试信息 + * + * 关键连接状态信息(PPP_DEBUG_CRITICAL)始终显示,不受此级别控制 + * + * 如需完整调试信息,可将上面的PPP_DEBUG_LEVEL改为PPP_DEBUG_LEVEL_DEBUG + */ + +/* PPP状态定义 */ +typedef enum { + PPP_STATE_IDLE = 0, // 空闲状态 + PPP_STATE_INITIALIZED, // 已初始化状态 + PPP_STATE_INIT, // 初始化状态 + PPP_STATE_AT_HANDSHAKE, // AT握手状态 + PPP_STATE_SET_BAUDRATE, // 设置波特率状态 + PPP_STATE_DIALING, // 拨号中 + PPP_STATE_CONNECTING, // 连接中 + PPP_STATE_CONNECTED, // 已连接状态 + PPP_STATE_DISCONNECTING, // 断开连接状态 + PPP_STATE_ERROR, // 错误状态 + PPP_STATE_MAX +} ppp_state_t; + +/* PPP错误码定义 */ +typedef enum { + PPP_ERR_OK = 0, // 成功 + PPP_ERR_TIMEOUT, // 超时 + PPP_ERR_AT_FAILED, // AT命令失败 + PPP_ERR_DIAL_FAILED, // 拨号失败 + PPP_ERR_NO_CARRIER, // 无载波 + PPP_ERR_BUSY, // 忙 + PPP_ERR_NO_DIALTONE, // 无拨号音 + PPP_ERR_INVALID_PARAM, // 无效参数 + PPP_ERR_MEMORY, // 内存错误 + PPP_ERR_UART, // 串口错误 + PPP_ERR_INVALID_STATE, // 无效状态 + PPP_ERR_UNKNOWN, // 未知错误 + PPP_ERR_MAX +} ppp_error_t; + +#ifdef __cplusplus +} +#endif + +#endif /* PPP_CONFIG_H */ \ No newline at end of file diff --git a/code/sdk/zhihui/n706a_net/include/ppp_debug.h b/code/sdk/zhihui/n706a_net/include/ppp_debug.h new file mode 100644 index 000000000..ecf7b4ab4 --- /dev/null +++ b/code/sdk/zhihui/n706a_net/include/ppp_debug.h @@ -0,0 +1,110 @@ +#ifndef PPP_DEBUG_H +#define PPP_DEBUG_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include "ppp_config.h" +#include "debug_print.h" + +/* 调试级别定义 */ +#define PPP_DEBUG_LEVEL_NONE 0 +#define PPP_DEBUG_LEVEL_ERROR 1 +#define PPP_DEBUG_LEVEL_WARN 2 +#define PPP_DEBUG_LEVEL_INFO 3 +#define PPP_DEBUG_LEVEL_DEBUG 4 + +#if PPP_DEBUG_ENABLE + +/* 前向声明动态调试级别函数 */ +extern int ppp_debug_get_level(void); + +#define PPP_DEBUG_ERROR(fmt, ...) \ + do { \ + if (ppp_debug_get_level() >= PPP_DEBUG_LEVEL_ERROR) { \ + PRINT("[PPP ERROR] " fmt "\r\n", ##__VA_ARGS__); \ + } \ + } while(0) + +#define PPP_DEBUG_WARN(fmt, ...) \ + do { \ + if (ppp_debug_get_level() >= PPP_DEBUG_LEVEL_WARN) { \ + PRINT("[PPP WARN] " fmt "\r\n", ##__VA_ARGS__); \ + } \ + } while(0) + +#define PPP_DEBUG_INFO(fmt, ...) \ + do { \ + if (ppp_debug_get_level() >= PPP_DEBUG_LEVEL_INFO) { \ + PRINT("[PPP INFO] " fmt "\r\n", ##__VA_ARGS__); \ + } \ + } while(0) + +#define PPP_DEBUG_DEBUG(fmt, ...) \ + do { \ + if (ppp_debug_get_level() >= PPP_DEBUG_LEVEL_DEBUG) { \ + PRINT("[PPP DEBUG] " fmt "\r\n", ##__VA_ARGS__); \ + } \ + } while(0) + +#define PPP_DEBUG_HEXDUMP(data, len) \ + do { \ + if (ppp_debug_get_level() >= PPP_DEBUG_LEVEL_DEBUG) { \ + ppp_debug_hexdump(data, len); \ + } \ + } while(0) + +/* 兼容性宏 - 用于旧代码 */ +#define PPP_DEBUG_PRINT(fmt, ...) \ + do { \ + if (ppp_debug_get_level() >= PPP_DEBUG_LEVEL_INFO) { \ + print_str(fmt, ##__VA_ARGS__); \ + } \ + } while(0) + +/* 关键信息宏 - 始终打印重要的连接状态信息 */ +#define PPP_DEBUG_CRITICAL(fmt, ...) \ + do { \ + PRINT("[PPP] " fmt "\r\n", ##__VA_ARGS__); \ + } while(0) + +#else + +#define PPP_DEBUG_ERROR(fmt, ...) +#define PPP_DEBUG_WARN(fmt, ...) +#define PPP_DEBUG_INFO(fmt, ...) +#define PPP_DEBUG_DEBUG(fmt, ...) +#define PPP_DEBUG_HEXDUMP(data, len) +#define PPP_DEBUG_PRINT(fmt, ...) +#define PPP_DEBUG_CRITICAL(fmt, ...) PRINT("[PPP] " fmt "\r\n", ##__VA_ARGS__) + +#endif /* PPP_DEBUG_ENABLE */ + +/** + * @brief 十六进制数据转储 + * @param data 数据指针 + * @param len 数据长度 + */ +void ppp_debug_hexdump(const uint8_t *data, uint32_t len); + +/** + * @brief 获取错误码字符串 + * @param error_code 错误码 + * @return 错误码字符串 + */ +const char *ppp_debug_error_string(ppp_error_t error_code); + +/** + * @brief 获取状态字符串 + * @param state PPP状态 + * @return 状态字符串 + */ +const char *ppp_debug_state_string(ppp_state_t state); + +#ifdef __cplusplus +} +#endif + +#endif /* PPP_DEBUG_H */ \ No newline at end of file diff --git a/code/sdk/zhihui/n706a_net/include/ppp_debug_manager.h b/code/sdk/zhihui/n706a_net/include/ppp_debug_manager.h new file mode 100644 index 000000000..a92f872e7 --- /dev/null +++ b/code/sdk/zhihui/n706a_net/include/ppp_debug_manager.h @@ -0,0 +1,61 @@ +/** + * @file ppp_debug_manager.h + * @brief PPP调试级别动态管理模块头文件 + */ + +#ifndef PPP_DEBUG_MANAGER_H +#define PPP_DEBUG_MANAGER_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include "ppp_types.h" + +/** + * @brief 获取当前动态调试级别 + * @return 当前调试级别 + */ +int ppp_debug_get_level(void); + +/** + * @brief 设置动态调试级别 + * @param level 调试级别 (0-4) + * @return PPP_ERR_OK-成功,其他-失败 + */ +ppp_error_t ppp_debug_set_level(int level); + +/** + * @brief 获取调试级别名称 + * @param level 调试级别 + * @return 级别名称字符串 + */ +const char* ppp_debug_get_level_name(int level); + +/** + * @brief 根据名称设置调试级别 + * @param level_name 级别名称 ("NONE", "ERROR", "WARN", "INFO", "DEBUG") + * @return PPP_ERR_OK-成功,其他-失败 + */ +ppp_error_t ppp_debug_set_level_by_name(const char* level_name); + +/** + * @brief 重置调试级别为默认值 + * @return PPP_ERR_OK-成功 + */ +ppp_error_t ppp_debug_reset_level(void); + +/** + * @brief 检查指定级别是否启用 + * @param level 要检查的级别 + * @return true-启用,false-未启用 + */ +bool ppp_debug_is_level_enabled(int level); + +#ifdef __cplusplus +} +#endif + +#endif /* PPP_DEBUG_MANAGER_H */ \ No newline at end of file diff --git a/code/sdk/zhihui/n706a_net/include/ppp_interrupt.h b/code/sdk/zhihui/n706a_net/include/ppp_interrupt.h new file mode 100644 index 000000000..24452b175 --- /dev/null +++ b/code/sdk/zhihui/n706a_net/include/ppp_interrupt.h @@ -0,0 +1,153 @@ +/** + * @file ppp_interrupt.h + * @brief PPP UART中断接收处理头文件 + */ + +#ifndef PPP_INTERRUPT_H +#define PPP_INTERRUPT_H + +#include "ppp_types.h" +#include "uart.h" +#include "pinctrl.h" +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief PPP中断统计信息 + */ +typedef struct { + uint32_t total_interrupts; // 总中断次数 + uint32_t success_count; // 成功处理次数 + uint32_t error_count; // 错误次数 + uint32_t lwip_error_count; // lwIP错误次数 + + uint32_t total_bytes_received; // 总接收字节数 + uint32_t total_bytes_sent_to_lwip; // 发送到lwIP的字节数 + + uint32_t dropped_not_initialized; // 因PPP未初始化丢弃的字节数 + uint32_t dropped_lwip_error; // 因lwIP错误丢弃的字节数 + uint32_t dropped_malloc_fail; // 因内存分配失败丢弃的字节数 + uint32_t dropped_queue_full; // 因消息队列满丢弃的字节数 +} ppp_interrupt_stats_t; + +/** + * @brief UART接收中断回调函数(由UART驱动调用) + * @param buffer 接收数据缓冲区 + * @param length 数据长度 + * @param error 是否有错误 + */ +void ppp_uart_rx_interrupt_handler(const void *buffer, uint16_t length, bool error); + +/** + * @brief 启用PPP UART中断接收 + * @return PPP_ERR_OK 成功,其他值失败 + * + * @note 调用此函数前: + * 1. ppp_serial必须已初始化(UART总线号从ppp_serial_get_uart_bus()获取) + * 2. UART必须已经初始化(硬件缓冲区在uart_porting.c中配置) + */ +ppp_error_t ppp_interrupt_enable(void); + +/** + * @brief 禁用PPP UART中断接收 + * @return PPP_ERR_OK 成功,其他值失败 + */ +ppp_error_t ppp_interrupt_disable(void); + +/** + * @brief 检查中断是否已启用 + * @return true 已启用,false 未启用 + */ +bool ppp_interrupt_is_enabled(void); + +/** + * @brief 获取中断统计信息 + * @param stats 统计信息输出缓冲区 + */ +void ppp_interrupt_get_stats(ppp_interrupt_stats_t *stats); + +/** + * @brief 重置中断统计信息 + */ +void ppp_interrupt_reset_stats(void); + +/** + * @brief 打印中断统计信息 + */ +void ppp_interrupt_print_stats(void); + +/** + * @brief 检查是否可以安全执行+++命令切换到命令模式 + * @return true 安全,false 不安全 + * + * 判断标准: + * 1. PPP已连接 + * 2. 接收空闲时间 >= 5秒 + * 3. 接收速率 < 100 bps + * 4. 所有buffer都是EMPTY状态 + */ +bool ppp_interrupt_is_safe_for_command_switch(void); + +/** + * @brief 等待安全切换窗口 + * @param timeout_ms 超时时间(毫秒) + * @return true 找到安全窗口,false 超时 + */ +bool ppp_interrupt_wait_for_safe_switch(uint32_t timeout_ms); + +/** + * @brief 打印数据传输状态信息(调试用) + */ +void ppp_interrupt_print_data_state(void); + +/** + * @brief 暂停PPP中断处理(用于临时切换到AT模式) + * @return PPP_ERR_OK 成功,其他值失败 + * + * 极简实现(只需停止 UART 中断): + * 1. 注销UART中断回调 → 无新数据 + * 2. Timer 继续运行 → 自动 flush 残留数据 + * 3. 线程自然阻塞 → 使用 OSAL_MSGQ_WAIT_FOREVER + * + * 优势: + * - 代码极简(只有一行操作) + * - 自动处理(Timer 自动 flush,线程自动阻塞) + * - 资源保留(所有资源保持,包括 Timer) + * + * 注意: + * - 与 ppp_interrupt_disable() 不同,本函数保留所有资源 + * - 必须与 ppp_interrupt_resume() 配对使用 + * - Timer 持续运行保证数据按顺序 flush + */ +ppp_error_t ppp_interrupt_pause(void); + +/** + * @brief 恢复PPP中断处理(从AT模式返回) + * @return PPP_ERR_OK 成功,其他值失败 + * + * 极简实现(只需重新注册 UART 中断): + * 1. 注册UART中断回调 → 开始接收新数据 + * 2. 线程自动唤醒 → 从阻塞队列读取中返回 + * 3. Timer 一直在运行 → 无需重启 + * + * 优势: + * - 代码极简(只有一行操作) + * - 自动唤醒(线程自动响应新数据) + * - 无需清理(Timer 已经处理过了) + * + * 注意: + * - 与 ppp_interrupt_pause() 配对使用 + * - 不重新创建任何资源,只恢复运行状态 + * - Buffer 已经被 Timer 自动 flush 干净 + */ +ppp_error_t ppp_interrupt_resume(void); + +#ifdef __cplusplus +} +#endif + +#endif /* PPP_INTERRUPT_H */ \ No newline at end of file diff --git a/code/sdk/zhihui/n706a_net/include/ppp_power_ctl.h b/code/sdk/zhihui/n706a_net/include/ppp_power_ctl.h new file mode 100644 index 000000000..687e4708c --- /dev/null +++ b/code/sdk/zhihui/n706a_net/include/ppp_power_ctl.h @@ -0,0 +1,130 @@ +/* + * @brief PPP模块电源控制接口 + * @description 提供PPP模块专用的4G电源控制功能,与SLIP模块互斥使用 + * Copyright (c) CompanyNameMagicTag 2024-2024. All rights reserved. + */ + +#ifndef __PPP_POWER_CTL_H +#define __PPP_POWER_CTL_H + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +#include +#include +#include "platform_core.h" +#include "errcode.h" + +/** + * @brief PPP模块4G电源控制引脚配置结构体 + */ +typedef struct { + pin_t bat_pin; // 控制Cat1 battery 的gpio,没有控制,设置为 PIN_NONE 即可 + uint8_t bat_pin_en_value; // battery引脚使能值 + pin_t pwr_pin; // 控制cat1开机键的gpio + uint8_t pwr_pin_en_value; // 电源引脚使能值 + pin_t reset_pin; // 控制cat1重置键的gpio + uint8_t reset_pin_en_value; // 复位引脚使能值 + pin_t ring_pin; // cat1唤醒蓝牙用的gpio + uint8_t ring_pin_en_value; // 唤醒引脚使能值 + pin_t wakemodem_pin; // 蓝牙唤醒cat1用的gpio + uint8_t wakemodem_pin_en_value; // 唤醒modem引脚使能值 +} ppp_ctl_pin_4g_t; + +/** + * @brief PPP模块GPIO初始化 + */ +void ppp_api_gpio_init(void); + +/** + * @brief PPP模块UART初始化 + */ +void ppp_api_uart_init(void); + +/** + * @brief PPP模块设置电源引脚状态 + * @param pwron 1-开启电源,0-关闭电源 + * @return 0-成功,其他-失败 + */ +uint32_t ppp_api_cat1_power_pin(uint32_t pwron); + +/** + * @brief PPP模块设置复位引脚状态 + * @param pwron 1-复位,0-释放复位 + * @return 0-成功,其他-失败 + */ +uint32_t ppp_api_cat1_reset_pin(uint32_t pwron); + +/** + * @brief PPP模块设置电池引脚状态 + * @param pwron 1-开启电池,0-关闭电池 + * @return 0-成功,其他-失败 + */ +uint32_t ppp_api_cat1_bat_pin(uint32_t pwron); + +/** + * @brief PPP模块4G上电序列 + */ +void ppp_power_on_cat1(void); + +/** + * @brief PPP模块4G下电序列 + */ +void ppp_power_off_cat1(void); + +/** + * @brief PPP模块4G重置序列 + */ +void ppp_power_reset_cat1(void); + +/** + * @brief PPP模块检查4G是否使能 + * @return true-已使能,false-未使能 + */ +bool ppp_api_is_cat1_enabled(void); + +/** + * @brief PPP模块唤醒Cat1 + * @param wakeup 1-唤醒,0-不唤醒 + */ +void ppp_api_wakeup_cat1(uint8_t wakeup); + +/** + * @brief PPP模块保持唤醒Cat1 + * @param keep_wakeup 1-保持唤醒,0-允许休眠 + */ +void ppp_api_keep_wakeup(uint8_t keep_wakeup); + + +/** + * @brief PPP模块注册4G控制引脚配置 + * @param ctl_pin 引脚配置结构体指针 + * @return ERRCODE_SUCC-成功,其他-失败 + */ +errcode_t ppp_register_ctl_pin(ppp_ctl_pin_4g_t *ctl_pin); + +/** + * @brief PPP模块Cat1唤醒中断回调函数 + * @param pin 中断引脚 + * @param param 参数 + */ +void ppp_ring_isr_callback(pin_t pin, uintptr_t param); + +/** + * @brief PPP模块专用的4G使能控制接口 + * @param isopen true-开启4G,false-关闭4G + * @note 此接口与SLIP模块互斥,只能有一个模块控制4G电源 + */ +void ppp_api_enable_cat1(bool isopen); + +/** + * @brief PPP模块反初始化 + */ +void ppp_api_deinit(void); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __PPP_POWER_CTL_H */ \ No newline at end of file diff --git a/code/sdk/zhihui/n706a_net/include/ppp_serial.h b/code/sdk/zhihui/n706a_net/include/ppp_serial.h new file mode 100644 index 000000000..316e50685 --- /dev/null +++ b/code/sdk/zhihui/n706a_net/include/ppp_serial.h @@ -0,0 +1,176 @@ +/** + * @brief PPP串口模块头文件 - 统一缓冲区管理 + PPP模式智能发送缓冲 + */ + +#ifndef PPP_SERIAL_H +#define PPP_SERIAL_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include "ppp_types.h" + +/* 串口接收回调函数类型 */ +typedef void (*ppp_serial_rx_callback_t)(const uint8_t *data, uint16_t length, void *arg); + +/* 串口工作模式 */ +typedef enum { + PPP_SERIAL_MODE_NONE = 0, // 未初始化 + PPP_SERIAL_MODE_AT, // AT命令模式(直接使用UART) + PPP_SERIAL_MODE_PPP_DATA // PPP数据模式(使用UART + 软件缓冲区 + 智能发送缓冲) +} ppp_serial_mode_t; + +/** + * @brief 串口配置结构 + */ +typedef struct { + uint8_t uart_bus; // UART总线号 + uint8_t irq_num; // UART IRQ引脚 + uint32_t baudrate; // 波特率 + uint8_t tx_pin; // 发送引脚 + uint8_t rx_pin; // 接收引脚 + uint32_t rx_buffer_size; // 接收缓冲区大小 + uint32_t tx_buffer_size; // 发送缓冲区大小(PPP模式使用) + ppp_serial_rx_callback_t rx_callback; // 接收回调函数 + void *rx_callback_arg; // 回调函数参数 +} ppp_serial_config_t; + +/** + * @brief 串口统计信息 + */ +typedef struct { + uint32_t tx_bytes; // 发送字节数 + uint32_t rx_bytes; // 接收字节数 + uint32_t tx_errors; // 发送错误数 + uint32_t rx_errors; // 接收错误数 + uint32_t rx_buffer_usage; // 接收缓冲区使用量 + uint32_t rx_buffer_size; // 接收缓冲区大小 + uint32_t tx_buffer_usage; // 发送缓冲区使用量 + uint32_t tx_buffer_size; // 发送缓冲区大小 +} ppp_serial_stats_t; + +/** + * @brief 初始化PPP串口(统一缓冲区管理) + * @param config 串口配置 + * @return 错误码 + */ +ppp_error_t ppp_serial_init(const ppp_serial_config_t *config); + +/** + * @brief 去初始化PPP串口 + */ +void ppp_serial_deinit(void); + +/** + * @brief 切换到PPP数据模式(启用软件缓冲区管理) + * @return 错误码 + */ +ppp_error_t ppp_serial_switch_to_ppp_mode(void); + +/* ppp_serial_switch_to_at_mode() 已删除 - N706A_NET始终处于AT模式 */ +/* 如需清空缓冲区,请使用 ppp_serial_flush_rx() */ + +/** + * @brief 获取当前串口模式 + * @return 当前模式 + */ +ppp_serial_mode_t ppp_serial_get_mode(void); + +/** + * @brief 发送数据(AT模式直接发送,PPP模式使用智能缓冲发送) + * @param data 数据指针 + * @param length 数据长度 + * @return 错误码 + */ +ppp_error_t ppp_serial_write(const uint8_t *data, uint32_t length); + +/** + * @brief 读取数据(AT模式直接从UART读取,PPP模式从软件缓冲区读取) + * @param buffer 接收缓冲区 + * @param buffer_size 缓冲区大小 + * @param bytes_read 实际读取字节数 + * @return 错误码 + */ +ppp_error_t ppp_serial_read(uint8_t *buffer, uint32_t buffer_size, uint32_t *bytes_read); + +/** + * @brief PPP快速尝试读取 - 类似SLIP的sio_tryread,使用1ms超时 + * @param buffer 接收缓冲区 + * @param buffer_size 缓冲区大小 + * @param bytes_read 实际读取字节数 + * @return 错误码 + */ +ppp_error_t ppp_serial_tryread(uint8_t *buffer, uint32_t buffer_size, uint32_t *bytes_read); + +/** + * @brief 等待数据接收 + * @param timeout_ms 超时时间(毫秒) + * @return 错误码 + */ +ppp_error_t ppp_serial_wait_data(uint32_t timeout_ms); + +/** + * @brief 获取可读数据长度 + * @return 可读数据长度 + */ +uint32_t ppp_serial_available(void); + +/** + * @brief 清空接收缓冲区 + */ +void ppp_serial_flush_rx(void); + +/* TX buffer removed - 发送改为直接UART写入,无需buffer和flush */ + +/** + * @brief 设置接收回调函数 + * @param callback 回调函数 + * @param arg 回调函数参数 + * @return 错误码 + */ +ppp_error_t ppp_serial_set_rx_callback(ppp_serial_rx_callback_t callback, void *arg); + +/** + * @brief 获取串口统计信息 + * @param stats 统计信息结构指针 + * @return 错误码 + */ +ppp_error_t ppp_serial_get_stats(ppp_serial_stats_t *stats); + +/** + * @brief 获取UART总线号 + * @return UART总线号,如果未初始化返回UART_BUS_0 + */ +uint8_t ppp_serial_get_uart_bus(void); + +/** + * @brief PPP中断接收回调函数 - 用于高效数据接收 + * @param buffer 接收到的数据缓冲区 + * @param length 数据长度 + * @param error 是否有错误 + * @note 此函数应该在UART中断回调中调用,类似SLIP的uart_read_data_int_handler + */ +void ppp_uart_rx_interrupt_handler(const void *buffer, uint16_t length, bool error); + +/** + * @brief 获取中断接收队列统计信息 + * @param queue_count 队列中的数据块数量 + * @param bytes_queued 队列中的字节数 + * @param chunks_dropped 丢弃的数据块数量 + */ +void ppp_get_interrupt_rx_stats(uint32_t *queue_count, uint32_t *bytes_queued, uint32_t *chunks_dropped); + +/** + * @brief 重置串口状态 + */ +ppp_error_t ppp_serial_reset(void); + + +#ifdef __cplusplus +} +#endif + +#endif /* PPP_SERIAL_H */ \ No newline at end of file diff --git a/code/sdk/zhihui/n706a_net/include/ppp_types.h b/code/sdk/zhihui/n706a_net/include/ppp_types.h new file mode 100644 index 000000000..7d865e872 --- /dev/null +++ b/code/sdk/zhihui/n706a_net/include/ppp_types.h @@ -0,0 +1,165 @@ +#ifndef PPP_TYPES_H +#define PPP_TYPES_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include "ppp_config.h" + +/* 前向声明,避免直接包含lwIP头文件 */ +struct netif; +struct ip4_addr; + +/* PPP配置结构(简化版,用于新API) */ +typedef struct { + char apn[64]; // APN接入点名称 + char username[32]; // 用户名 + char password[32]; // 密码 + uint32_t baudrate; // 串口波特率 + bool auto_baudrate; // 是否自动波特率检测 + uint32_t dial_timeout; // 拨号超时时间(ms) + uint8_t retry_count; // 重试次数 +} ppp_config_t; + +/* PPP连接信息结构(简化版,用于新API) */ +typedef struct { + uint32_t local_ip; // 本地IP地址 + uint32_t remote_ip; // 远程IP地址 + uint32_t primary_dns; // 主DNS服务器 + uint32_t secondary_dns; // 辅DNS服务器 + uint16_t mtu; // MTU大小 + bool connected; // 连接状态 + uint32_t connect_time; // 连接时间(秒) +} ppp_connection_info_t; + +/* PPP拨号参数结构(原有的,保持兼容性) */ +typedef struct { + char apn[64]; // APN接入点名称 + char username[32]; // 用户名 + char password[32]; // 密码 + uint32_t baudrate; // 串口波特率 + bool auto_baudrate; // 是否自动波特率检测 + uint32_t dial_timeout; // 拨号超时时间(ms) + uint8_t retry_count; // 重试次数 +} ppp_dial_config_t; + +/* PPP网络信息结构 */ +typedef struct { + uint32_t local_ip; // 本地IP地址 + uint32_t remote_ip; // 远程IP地址 + uint32_t primary_dns; // 主DNS服务器 + uint32_t secondary_dns; // 辅DNS服务器 + uint32_t netmask; // 子网掩码 + uint32_t gateway; // 网关地址 + uint16_t mtu; // MTU大小 + bool connected; // 连接状态 +} ppp_network_info_t; + +/* PPP统计信息结构 */ +typedef struct { + uint32_t tx_packets; // 发送包数 + uint32_t rx_packets; // 接收包数 + uint32_t tx_bytes; // 发送字节数 + uint32_t rx_bytes; // 接收字节数 + uint32_t tx_errors; // 发送错误数 + uint32_t rx_errors; // 接收错误数 + uint32_t connect_time; // 连接时间(秒) + uint32_t last_activity; // 最后活动时间戳 +} ppp_stats_t; + +/* PPP字节丢弃统计结构 */ +typedef struct { + uint32_t total_bytes_received; // 总接收字节数 + uint32_t total_bytes_processed; // 总处理字节数 + uint32_t total_bytes_discarded; // 总丢弃字节数 + uint32_t invalid_frame_bytes; // 无效帧丢弃字节数 + uint32_t queue_full_bytes; // 队列满丢弃字节数 + uint32_t malloc_fail_bytes; // 内存分配失败丢弃字节数 + uint32_t oversized_frame_bytes; // 超大帧丢弃字节数 + uint32_t incomplete_frame_bytes; // 不完整帧丢弃字节数 + uint32_t total_frames_received; // 总接收帧数 + uint32_t total_frames_processed; // 总处理帧数 + uint32_t total_frames_discarded; // 总丢弃帧数 +} ppp_discard_stats_t; + +/* PPP事件类型 */ +typedef enum { + PPP_EVENT_CONNECTED = 0, // 连接成功 + PPP_EVENT_DISCONNECTED, // 连接断开 + PPP_EVENT_ERROR, // 发生错误 + PPP_EVENT_IP_ACQUIRED, // 获取到IP地址 + PPP_EVENT_DNS_ACQUIRED, // 获取到DNS地址 + PPP_EVENT_AT_RESPONSE, // AT命令响应 + PPP_EVENT_DATA_RECEIVED // 接收到数据 +} ppp_event_type_t; + +/* PPP事件数据结构 */ +typedef struct { + ppp_event_type_t type; // 事件类型 + ppp_error_t error_code; // 错误码 + void *data; // 事件数据 + uint32_t data_len; // 数据长度 +} ppp_event_t; + +/* PPP事件回调函数类型 */ +typedef void (*ppp_event_callback_t)(const ppp_event_t *event); + +/* PPP状态枚举 - 定义在ppp_config.h中 */ + +/* PPP控制块结构 */ +typedef struct { + ppp_state_t state; // 当前状态 + ppp_dial_config_t config; // 拨号配置 + ppp_network_info_t network; // 网络信息 + ppp_stats_t stats; // 统计信息 + + /* lwIP相关 */ + struct netif *netif; // 网络接口指针 + void *ppp_pcb; // PPP控制块 + + /* 串口相关 */ + uint8_t uart_bus; // 串口总线 + uint8_t *rx_buffer; // 接收缓冲区 + uint8_t *tx_buffer; // 发送缓冲区 + uint32_t rx_buffer_size; // 接收缓冲区大小 + uint32_t tx_buffer_size; // 发送缓冲区大小 + + /* 同步相关 */ + void *mutex; // 互斥锁 + void *semaphore; // 信号量 + void *task_handle; // 任务句柄 + + /* 回调函数 */ + ppp_event_callback_t event_callback; // 事件回调函数 + + /* 内部状态 */ + uint32_t last_at_time; // 最后AT命令时间 + uint32_t connect_start_time; // 连接开始时间 + bool initialized; // 初始化标志 + bool running; // 运行标志 +} ppp_context_t; + +/* AT命令结构 */ +typedef struct { + char command[PPP_AT_CMD_MAX_LEN]; // AT命令 + char expected_response[64]; // 期望响应 + uint32_t timeout; // 超时时间(ms) + uint8_t retry_count; // 重试次数 +} ppp_at_cmd_t; + +/* AT响应结构 */ +typedef struct { + char response[PPP_AT_RESPONSE_MAX_LEN]; // 响应内容 + uint32_t length; // 响应长度 + bool success; // 是否成功 + ppp_error_t error_code; // 错误码 +} ppp_at_response_t; + +#ifdef __cplusplus +} +#endif + +#endif /* PPP_TYPES_H */ \ No newline at end of file diff --git a/code/sdk/zhihui/n706a_net/src/n706a_at_handler.c b/code/sdk/zhihui/n706a_net/src/n706a_at_handler.c new file mode 100644 index 000000000..089b754b9 --- /dev/null +++ b/code/sdk/zhihui/n706a_net/src/n706a_at_handler.c @@ -0,0 +1,1278 @@ +#include "n706a_at_handler.h" +#include "ppp_config.h" +#include "ppp_types.h" +#include "ppp_debug.h" +#include "ppp_serial.h" +#include "uart.h" +#include "schedule/osal_task.h" +#include "lock/osal_mutex.h" +#include "semaphore/osal_semaphore.h" +#include "soc_osal.h" +#include "securec.h" +#include +#include +#include // For malloc and free + +/* ========== AT响应异步缓冲区(由ppp_interrupt.c写入) ========== */ +#define N706A_AT_RESPONSE_RING_BUFFER_SIZE 4096 // 4KB环形缓冲区 + +static uint8_t g_at_response_ring_buffer[N706A_AT_RESPONSE_RING_BUFFER_SIZE]; +static volatile uint32_t g_at_response_head = 0; +static volatile uint32_t g_at_response_tail = 0; +static osal_mutex g_at_response_mutex; +static osal_semaphore g_at_response_sem; + +/* AT命令处理上下文 */ +static struct { + bool initialized; + uint8_t uart_bus; + uint32_t current_baudrate; + uint32_t target_baudrate; + bool auto_baudrate_mode; + ppp_at_response_t last_response; + osal_mutex at_mutex; // 改为结构体,不是指针 +} g_at_ctx = {0}; + +/* 支持的波特率列表 */ +static const uint32_t g_auto_baudrates[] = PPP_BAUDRATE_AUTO_LIST; +static const uint32_t g_fixed_baudrates[] = PPP_BAUDRATE_FIXED_LIST; + +/* 全局AT响应缓存 - 1024字节动态分配 */ +static uint8_t *g_at_response_buffer = NULL; +static uint32_t g_at_buffer_size = 1024; + +/* TCP URC回调 */ +static n706a_tcp_urc_callback_t g_tcp_urc_callback = NULL; + +/* AT命令响应解析 */ +static bool ppp_at_parse_response(const char *response, const char *expected); +static ppp_error_t ppp_at_wait_response(ppp_at_response_t *response, uint32_t timeout); +static ppp_error_t ppp_at_set_baudrate(uint32_t baudrate); +static ppp_error_t ppp_at_send_command(const char *command, const char *expected_response, + uint32_t timeout, ppp_at_response_t *response); + +/* URC解析函数 */ +static bool n706a_parse_tcprecv_urc(const char *urc_data, size_t urc_len, + int *socket_id, size_t *data_len, const uint8_t **data); + +/** + * @brief 写入AT响应数据到环形缓冲区(由ppp_interrupt.c调用) + * @param data 数据指针 + * @param length 数据长度 + */ +void n706a_at_response_buffer_write(const uint8_t *data, uint32_t length) +{ + if (!data || length == 0) { + return; + } + + // 检测是否是 +TCPRECV URC(TCP数据接收通知) + // 格式:+TCPRECV: ,,\r\n + if (length >= 10 && memcmp(data, "+TCPRECV: ", 10) == 0 && g_tcp_urc_callback) { + int socket_id; + size_t data_len; + const uint8_t *tcp_data; + + if (n706a_parse_tcprecv_urc((const char *)data, length, &socket_id, &data_len, &tcp_data)) { + PPP_DEBUG_INFO("TCP URC detected: socket=%d, len=%u", socket_id, (unsigned int)data_len); + + // 立即回调应用层(在中断上下文或处理线程中) + g_tcp_urc_callback(socket_id, tcp_data, data_len); + + // URC已处理,不需要写入AT响应缓冲区 + return; + } else { + PPP_DEBUG_WARN("Failed to parse +TCPRECV URC"); + } + } + + // 普通AT响应或无法识别的数据,写入环形缓冲区 + osal_mutex_lock(&g_at_response_mutex); + + for (uint32_t i = 0; i < length; i++) { + g_at_response_ring_buffer[g_at_response_head] = data[i]; + g_at_response_head = (g_at_response_head + 1) % N706A_AT_RESPONSE_RING_BUFFER_SIZE; + + // 检查溢出(缓冲区满时丢弃最老的数据) + if (g_at_response_head == g_at_response_tail) { + g_at_response_tail = (g_at_response_tail + 1) % N706A_AT_RESPONSE_RING_BUFFER_SIZE; + PPP_DEBUG_WARN("AT response buffer overflow, dropping oldest byte"); + } + } + + osal_mutex_unlock(&g_at_response_mutex); + + // 通知有新数据到达 + osal_sem_up(&g_at_response_sem); + + PPP_DEBUG_DEBUG("AT response buffer: wrote %u bytes", length); +} + +/** + * @brief 初始化AT命令处理模块 + */ +ppp_error_t ppp_at_handler_init(uint8_t uart_bus, uint32_t current_baudrate, bool auto_baudrate_mode) +{ + if (g_at_ctx.initialized) { + PPP_DEBUG_WARN("AT handler already initialized"); + return PPP_ERR_OK; + } + + g_at_ctx.uart_bus = uart_bus; + g_at_ctx.current_baudrate = current_baudrate; + g_at_ctx.auto_baudrate_mode = auto_baudrate_mode; + + /* 创建AT命令互斥锁 */ + if (osal_mutex_init(&g_at_ctx.at_mutex) != OSAL_SUCCESS) { + PPP_DEBUG_ERROR("Failed to create AT mutex"); + return PPP_ERR_MEMORY; + } + + /* 创建AT响应缓冲区互斥锁 */ + if (osal_mutex_init(&g_at_response_mutex) != OSAL_SUCCESS) { + PPP_DEBUG_ERROR("Failed to create AT response mutex"); + osal_mutex_destroy(&g_at_ctx.at_mutex); + return PPP_ERR_MEMORY; + } + + /* 创建AT响应信号量 */ + if (osal_sem_init(&g_at_response_sem, 0) != OSAL_SUCCESS) { + PPP_DEBUG_ERROR("Failed to create AT response semaphore"); + osal_mutex_destroy(&g_at_ctx.at_mutex); + osal_mutex_destroy(&g_at_response_mutex); + return PPP_ERR_MEMORY; + } + + /* 初始化环形缓冲区 */ + g_at_response_head = 0; + g_at_response_tail = 0; + memset(g_at_response_ring_buffer, 0, sizeof(g_at_response_ring_buffer)); + + /* 分配全局AT响应缓存 */ + g_at_response_buffer = (uint8_t *)malloc(g_at_buffer_size); + if (g_at_response_buffer == NULL) { + PPP_DEBUG_ERROR("Failed to allocate AT response buffer"); + osal_mutex_destroy(&g_at_ctx.at_mutex); + osal_mutex_destroy(&g_at_response_mutex); + osal_sem_destroy(&g_at_response_sem); + return PPP_ERR_MEMORY; + } + + g_at_ctx.initialized = true; + PPP_DEBUG_INFO("n706a_net AT handler initialized on UART%d with async ring buffer", uart_bus); + + return PPP_ERR_OK; +} + +/** + * @brief 反初始化AT命令处理模块 + */ +void ppp_at_handler_deinit(void) +{ + if (!g_at_ctx.initialized) { + return; + } + + /* 销毁互斥锁和信号量 */ + osal_mutex_destroy(&g_at_ctx.at_mutex); + osal_mutex_destroy(&g_at_response_mutex); + osal_sem_destroy(&g_at_response_sem); + + /* 释放全局AT响应缓存 */ + if (g_at_response_buffer) { + free(g_at_response_buffer); + g_at_response_buffer = NULL; + } + + /* 清空环形缓冲区 */ + g_at_response_head = 0; + g_at_response_tail = 0; + + memset_s(&g_at_ctx, sizeof(g_at_ctx), 0, sizeof(g_at_ctx)); + PPP_DEBUG_INFO("n706a_net AT handler deinitialized"); +} + +/** + * @brief 执行波特率自适应握手 + * + * 进入自适应波特率,证明1000000没有正常工作,需要先reset,等5秒,开始设置自动握手的第一个波特率, + * 读取所有缓存数据丢掉,下达at指令,等待结果,重试3次,如果不成功,就设置第二个波特率,读取所有缓存数据丢掉, + * 下达at指令,等结果,重试3次,直至第三个波特率检测。如果都没有成功,则返回失败。 + * 如果任何一次at指令返回正确ok,直接在当前设置下,下达指 AT+IPR 配置 1000000 波特率,并返回成功。 + * 设置当前波特率为1000000,再次reset 模组,让其自动工作在 1000000波特率下。 + */ +ppp_error_t ppp_at_baudrate_handshake(void) +{ + if (!g_at_ctx.initialized) { + return PPP_ERR_INVALID_PARAM; + } + + PPP_DEBUG_INFO("Starting baudrate handshake (prove %d not working, then try auto handshake)",g_at_ctx.current_baudrate); + + // uint8_t dummy_buffer[256]={0}; + + // 2. 依次尝试自动波特率列表 + for (int i = 0; i < PPP_BAUDRATE_AUTO_COUNT; i++) { + uint32_t baudrate = g_auto_baudrates[i]; + PPP_DEBUG_DEBUG("=== Trying Baudrate %u (index %d/%d) ===", baudrate, i + 1, PPP_BAUDRATE_AUTO_COUNT); + + // 配置串口波特率 + uart_attr_t uart_attr = { + .baud_rate = baudrate, + .data_bits = UART_DATA_BIT_8, + .parity = UART_PARITY_NONE, + .stop_bits = UART_STOP_BIT_1, + }; + if (uapi_uart_set_attr(g_at_ctx.uart_bus, &uart_attr) != ERRCODE_SUCC) { + PPP_DEBUG_WARN("Failed to set baudrate %u", baudrate); + continue; + } + PPP_DEBUG_DEBUG("UART configured for baudrate %u", baudrate); + + // 清空接收缓冲区 + // PPP_DEBUG_DEBUG("Clearing UART RX buffer before AT test..."); + // int clear_cnt = 0; + // memset(dummy_buffer,0,sizeof(dummy_buffer)); + // while (uapi_uart_read(g_at_ctx.uart_bus, dummy_buffer, sizeof(dummy_buffer), 100) > 0) { + // clear_cnt++; + // } + // if (clear_cnt > 0) { + // PPP_DEBUG_DEBUG("Cleared %d RX buffer rounds", clear_cnt); + // } + + // 3次重试AT + ppp_error_t ret = PPP_ERR_AT_FAILED; + for (int retry = 0; retry < 2; retry++) { + PPP_DEBUG_DEBUG("--- AT Test at %u baud, retry %d/3 ---", baudrate, retry + 1); + + // 再次清空缓冲区,防止上次残留 + // memset(dummy_buffer,0,sizeof(dummy_buffer)); + // while (uapi_uart_read(g_at_ctx.uart_bus, dummy_buffer, sizeof(dummy_buffer), 100) > 0) {} + + ppp_at_response_t response; + ret = ppp_at_send_command("AT", "OK", 1000, &response); + + if (ret == PPP_ERR_OK) { + PPP_DEBUG_INFO("AT command successful at baudrate %u", baudrate); + + // 在当前波特率下,设置AT+IPR=1000000 + ret = ppp_at_set_baudrate(g_at_ctx.current_baudrate); + if (ret != PPP_ERR_OK) { + ret = ppp_at_set_baudrate(g_at_ctx.current_baudrate); + if (ret != PPP_ERR_OK) { + PPP_DEBUG_ERROR("Failed to set baudrate %u", g_at_ctx.current_baudrate); + return ret; + } + } + // 再次reset模组,让其自动工作在1000000波特率下 + PPP_DEBUG_DEBUG("Resetting module again to switch to baudrate %d...",g_at_ctx.current_baudrate); + // ppp_power_reset_cat1(); + // osal_msleep(5000); + ret = ppp_at_send_command("AT", "OK", 1000, &response); + if(ret != PPP_ERR_OK) { + PPP_DEBUG_INFO("Baudrate handshake failed, now at %d baud",g_at_ctx.current_baudrate); + }else{ + PPP_DEBUG_INFO("Baudrate handshake successful, now at %d baud",g_at_ctx.current_baudrate); + } + return PPP_ERR_OK; + } else { + PPP_DEBUG_WARN("AT command failed at baudrate %u, retry %d/3 (error: %d)", baudrate, retry + 1, ret); + osal_msleep(500); + } + } + + PPP_DEBUG_WARN("Baudrate %u failed after 3 retries, trying next...", baudrate); + osal_msleep(100); + } + + PPP_DEBUG_ERROR("Baudrate handshake failed: all baudrates tried, no success"); + return PPP_ERR_AT_FAILED; +} + +/** + * @brief 解析AT+IPR?响应,提取当前波特率 + * @param response AT+IPR?的响应字符串 + * @return 解析出的波特率,失败返回0 + */ +static uint32_t ppp_parse_ipr_response(const char *response) +{ + if (!response) { + return 0; + } + + PPP_DEBUG_DEBUG("Parsing IPR response: [%s]", response); + + /* 查找 "+IPR: " 字符串 */ + const char *ipr_pos = strstr(response, "+IPR: "); + if (!ipr_pos) { + PPP_DEBUG_WARN("IPR response format error: no '+IPR: ' found in response"); + return 0; + } + + /* 跳过 "+IPR: " 部分 */ + ipr_pos += 6; + + /* 跳过可能的空格 */ + while (*ipr_pos == ' ' || *ipr_pos == '\t') { + ipr_pos++; + } + + /* 解析波特率数字,只读取数字部分 */ + uint32_t baudrate = 0; + const char *num_start = ipr_pos; + + /* 找到数字的结束位置(遇到非数字字符停止) */ + while (*ipr_pos >= '0' && *ipr_pos <= '9') { + baudrate = baudrate * 10 + (*ipr_pos - '0'); + ipr_pos++; + } + + /* 检查是否成功解析到数字 */ + if (ipr_pos == num_start) { + PPP_DEBUG_WARN("IPR response format error: no valid number found after '+IPR: '"); + return 0; + } + + /* 验证波特率是否合理 */ + if (baudrate < 1200 || baudrate > 4000000) { + PPP_DEBUG_WARN("IPR response contains unreasonable baudrate: %u", baudrate); + return 0; + } + + PPP_DEBUG_DEBUG("Successfully parsed current baudrate from IPR response: %u", baudrate); + return baudrate; +} + +/** + * @brief 查询当前modem波特率 + * @return 当前modem波特率,失败返回0 + */ +static uint32_t ppp_query_current_modem_baudrate(void) +{ + ppp_at_response_t response; + ppp_error_t ret = ppp_at_send_command("AT+IPR?", "OK", PPP_AT_CMD_TIMEOUT, &response); + + if (ret != PPP_ERR_OK) { + PPP_DEBUG_WARN("Failed to query current baudrate with AT+IPR?, error: %d", ret); + return 0; + } + + return ppp_parse_ipr_response(response.response); +} + +/** + * @brief 设置固定波特率(内部函数) + */ +static ppp_error_t ppp_at_set_baudrate(uint32_t baudrate) +{ + if (!g_at_ctx.initialized) { + return PPP_ERR_INVALID_PARAM; + } + + /* 检查波特率是否支持 */ + bool supported = false; + for (int i = 0; i < PPP_BAUDRATE_FIXED_COUNT; i++) { + if (g_fixed_baudrates[i] == baudrate) { + supported = true; + break; + } + } + + if (!supported) { + PPP_DEBUG_ERROR("Unsupported baudrate: %u", baudrate); + return PPP_ERR_INVALID_PARAM; + } +#if defined(PPP_DO_SERIAL_TEST) && (PPP_DO_SERIAL_TEST == 1) + uint32_t current_modem_baudrate = baudrate; // 测试模式下,直接使用输入的波特率 +#else + /* 先查询当前modem波特率 */ + uint32_t current_modem_baudrate = ppp_query_current_modem_baudrate(); + if (current_modem_baudrate == baudrate) { + PPP_DEBUG_INFO("Modem already at target baudrate %u, skipping AT+IPR command", baudrate); + + /* 仍需要更新本地UART配置以确保一致性 */ + uart_attr_t uart_attr = { + .baud_rate = baudrate, + .data_bits = UART_DATA_BIT_8, + .parity = UART_PARITY_NONE, + .stop_bits = UART_STOP_BIT_1, + }; + + if (uapi_uart_set_attr(g_at_ctx.uart_bus, &uart_attr) != ERRCODE_SUCC) { + PPP_DEBUG_ERROR("Failed to update UART baudrate"); + return PPP_ERR_UART; + } + + g_at_ctx.current_baudrate = baudrate; + g_at_ctx.auto_baudrate_mode = false; + + PPP_DEBUG_DEBUG("Baudrate confirmed at %u (no change needed)", baudrate); + return PPP_ERR_OK; + } + + /* 需要设置新波特率 */ + PPP_DEBUG_INFO("Changing modem baudrate from %u to %u", current_modem_baudrate, baudrate); + + /* 发送AT+IPR命令设置波特率 */ + char cmd[64]; + snprintf_s(cmd, sizeof(cmd), sizeof(cmd) - 1, "AT+IPR=%u", baudrate); + + ppp_at_response_t response; + ppp_error_t ret = ppp_at_send_command(cmd, "OK", PPP_AT_CMD_TIMEOUT, &response); + + if (ret != PPP_ERR_OK) { + PPP_DEBUG_ERROR("Failed to set baudrate to %u", baudrate); + return ret; + } +#endif + /* 更新串口配置 */ + uart_attr_t uart_attr = { + .baud_rate = baudrate, + .data_bits = UART_DATA_BIT_8, + .parity = UART_PARITY_NONE, + .stop_bits = UART_STOP_BIT_1, + }; + + if (uapi_uart_set_attr(g_at_ctx.uart_bus, &uart_attr) != ERRCODE_SUCC) { + PPP_DEBUG_ERROR("Failed to update UART baudrate"); + return PPP_ERR_UART; + } + + g_at_ctx.current_baudrate = baudrate; + g_at_ctx.auto_baudrate_mode = false; + + PPP_DEBUG_DEBUG("Baudrate set bus(%d) to %u (changed from %u)", g_at_ctx.uart_bus, baudrate, current_modem_baudrate); + return PPP_ERR_OK; +} + +/** + * @brief 发送AT命令(内部函数) + * @note 检查内存泄露:所有malloc分配的内存都应在所有分支free + */ +static ppp_error_t ppp_at_send_command(const char *command, const char *expected_response, + uint32_t timeout, ppp_at_response_t *response) +{ + uint32_t start_timestamp = (uint32_t)osal_get_jiffies(); + + PPP_DEBUG_CRITICAL("=== AT COMMAND START ==="); + PPP_DEBUG_CRITICAL("Timestamp: %u", start_timestamp); + PPP_DEBUG_CRITICAL("Command: [%s]", command ? command : "NULL"); + PPP_DEBUG_CRITICAL("Expected: [%s]", expected_response ? expected_response : "NULL"); + PPP_DEBUG_CRITICAL("Timeout: %u ms", timeout); + + if (!g_at_ctx.initialized || !command || !response) { + PPP_DEBUG_ERROR("Invalid parameters: initialized=%d, command=%p, response=%p", + g_at_ctx.initialized, command, response); + return PPP_ERR_INVALID_PARAM; + } + + /* 防止系统卡死的安全措施 */ + const uint32_t MAX_COMMAND_TIMEOUT = 60000; // 绝对最大超时时间60秒 + if (timeout > MAX_COMMAND_TIMEOUT) { + PPP_DEBUG_WARN("Command timeout %u ms exceeds maximum %u ms, capping it", + timeout, MAX_COMMAND_TIMEOUT); + timeout = MAX_COMMAND_TIMEOUT; + } + + PPP_DEBUG_CRITICAL("Step 1: Attempting to acquire AT mutex..."); + uint32_t mutex_start = (uint32_t)osal_get_jiffies(); + + /* 使用带超时的互斥锁获取,防止永久阻塞 */ + int mutex_result = osal_mutex_lock_timeout(&g_at_ctx.at_mutex, timeout + 5000); + if (mutex_result != OSAL_SUCCESS) { + PPP_DEBUG_ERROR("Failed to acquire AT mutex within timeout, this indicates a serious problem"); + PPP_DEBUG_ERROR("Possible causes: 1) Previous AT command hung 2) Mutex corruption 3) Deadlock"); + return PPP_ERR_TIMEOUT; + } + + uint32_t mutex_end = (uint32_t)osal_get_jiffies(); + PPP_DEBUG_CRITICAL("Step 1: AT mutex acquired in %u ms", osal_jiffies_to_msecs(mutex_end - mutex_start)); + + PPP_DEBUG_DEBUG("=== AT Command Send ==="); + PPP_DEBUG_DEBUG("Command: %s", command); + PPP_DEBUG_DEBUG("Expected: %s", expected_response ? expected_response : "None"); + PPP_DEBUG_DEBUG("Timeout: %u ms", timeout); + + /* 清空响应缓冲区 */ + memset_s(response, sizeof(ppp_at_response_t), 0, sizeof(ppp_at_response_t)); + + /* 发送命令 */ + size_t command_len = strlen(command); + char *cmd_with_crlf = (char *)malloc(command_len + 4); + if (!cmd_with_crlf) { + PPP_DEBUG_ERROR("Failed to allocate memory for cmd_with_crlf"); + osal_mutex_unlock(&g_at_ctx.at_mutex); + return PPP_ERR_MEMORY; + } + snprintf_s(cmd_with_crlf, command_len + 4, command_len + 3, "%s\r\n", command); + + uint32_t cmd_len = strlen(cmd_with_crlf); + + /* 打印发送的字符串(可见字符) */ + char *printable_cmd = (char *)malloc(PPP_AT_CMD_MAX_LEN + 20); + if (printable_cmd == NULL) { + PPP_DEBUG_ERROR("Failed to allocate memory for printable command"); + free(cmd_with_crlf); // 防止内存泄露 + osal_mutex_unlock(&g_at_ctx.at_mutex); + return PPP_ERR_MEMORY; + } + + uint32_t j = 0; + for (uint32_t i = 0; i < cmd_len && j < (PPP_AT_CMD_MAX_LEN + 20) - 10; i++) { + if (cmd_with_crlf[i] == '\r') { + printable_cmd[j++] = '\\'; + printable_cmd[j++] = 'r'; + } else if (cmd_with_crlf[i] == '\n') { + printable_cmd[j++] = '\\'; + printable_cmd[j++] = 'n'; + } else if (cmd_with_crlf[i] >= 32 && cmd_with_crlf[i] <= 126) { + printable_cmd[j++] = cmd_with_crlf[i]; + } else { + j += snprintf_s(printable_cmd + j, (PPP_AT_CMD_MAX_LEN + 20) - j, (PPP_AT_CMD_MAX_LEN + 20) - j - 1, "\\x%02X", (uint8_t)cmd_with_crlf[i]); + } + } + printable_cmd[j] = '\0'; + PPP_DEBUG_DEBUG("Send String: %s", printable_cmd); + free(printable_cmd); + + PPP_DEBUG_CRITICAL("Step 2: Sending AT command via UART..."); + uint32_t uart_write_start = (uint32_t)osal_get_jiffies(); + + int32_t write_result = uapi_uart_write(g_at_ctx.uart_bus, (const uint8_t *)cmd_with_crlf, cmd_len, 0); + uint32_t uart_write_end = (uint32_t)osal_get_jiffies(); + + free(cmd_with_crlf); + + PPP_DEBUG_CRITICAL("Step 2: UART write completed in %u ms, result: %d (expected: %u)", + osal_jiffies_to_msecs(uart_write_end - uart_write_start), write_result, cmd_len); + + if (write_result != cmd_len) { + PPP_DEBUG_ERROR("Failed to send AT command: wrote %d bytes, expected %u", write_result, cmd_len); + osal_mutex_unlock(&g_at_ctx.at_mutex); + return PPP_ERR_UART; + } + + PPP_DEBUG_CRITICAL("Step 2: AT command sent successfully on bus %d", g_at_ctx.uart_bus); + + /* 等待响应 */ + PPP_DEBUG_CRITICAL("Step 3: Starting to wait for AT response..."); + uint32_t response_wait_start = (uint32_t)osal_get_jiffies(); + ppp_error_t ret = ppp_at_wait_response(response, timeout); + uint32_t response_wait_end = (uint32_t)osal_get_jiffies(); + PPP_DEBUG_CRITICAL("Step 3: AT response wait completed in %u ms, result: %d", + osal_jiffies_to_msecs(response_wait_end - response_wait_start), ret); + + /* 打印响应结果 */ + PPP_DEBUG_DEBUG("=== AT Command Response ==="); + if (ret == PPP_ERR_OK) { + PPP_DEBUG_DEBUG("Response received (%u bytes):", response->length); + + /* 打印接收的字符串(可见字符) */ + char *printable_resp = (char *)malloc(PPP_AT_RESPONSE_MAX_LEN + 100); + if (printable_resp != NULL) { + uint32_t j = 0; + for (uint32_t i = 0; i < response->length && j < (PPP_AT_RESPONSE_MAX_LEN + 100) - 10; i++) { + if (response->response[i] == '\r') { + printable_resp[j++] = '\\'; + printable_resp[j++] = 'r'; + } else if (response->response[i] == '\n') { + printable_resp[j++] = '\\'; + printable_resp[j++] = 'n'; + } else if (response->response[i] >= 32 && response->response[i] <= 126) { + printable_resp[j++] = response->response[i]; + } else { + j += snprintf_s(printable_resp + j, (PPP_AT_RESPONSE_MAX_LEN + 100) - j, (PPP_AT_RESPONSE_MAX_LEN + 100) - j - 1, "\\x%02X", (uint8_t)response->response[i]); + } + } + printable_resp[j] = '\0'; + PPP_DEBUG_DEBUG("Response String: %s", printable_resp); + free(printable_resp); + } + PPP_DEBUG_DEBUG("Raw Response: %s", response->response); + } else { + PPP_DEBUG_ERROR("No response received (error: %d)", ret); + } + + if (ret == PPP_ERR_OK) { + if (expected_response) { + if (!ppp_at_parse_response(response->response, expected_response)) { + PPP_DEBUG_WARN("Response validation failed - Expected: %s", expected_response); + PPP_DEBUG_WARN("Response validation failed - Received: %s", response->response); + response->success = false; + response->error_code = PPP_ERR_AT_FAILED; + ret = PPP_ERR_AT_FAILED; + } else { + PPP_DEBUG_DEBUG("Response validation successful - Expected: %s", expected_response); + response->success = true; + response->error_code = PPP_ERR_OK; + } + } else { + // 没有期望响应时,只要收到响应就认为成功 + response->success = true; + response->error_code = PPP_ERR_OK; + } + } else { + response->success = false; + response->error_code = ret; + } + + PPP_DEBUG_DEBUG("=== AT Command Complete (Result: %s) ===", ret == PPP_ERR_OK ? "SUCCESS" : "FAILED"); + + PPP_DEBUG_CRITICAL("Step 4: Releasing AT mutex..."); + uint32_t mutex_unlock_start = (uint32_t)osal_get_jiffies(); + osal_mutex_unlock(&g_at_ctx.at_mutex); + uint32_t mutex_unlock_end = (uint32_t)osal_get_jiffies(); + + uint32_t total_time = osal_jiffies_to_msecs(mutex_unlock_end - start_timestamp); + PPP_DEBUG_CRITICAL("Step 4: AT mutex released in %u ms", osal_jiffies_to_msecs(mutex_unlock_end - mutex_unlock_start)); + PPP_DEBUG_CRITICAL("=== AT COMMAND END (Total: %u ms, Result: %d) ===", total_time, ret); + + return ret; +} + +/** + * @brief 发送原始数据(不带AT命令格式) + * @note 用于 TCP/SSL 数据发送,在 AT+TCPSEND/AT+SSLTCPSEND 命令后使用 + */ +ppp_error_t ppp_at_send_raw_data(const void *data, size_t len, uint32_t timeout) +{ + if (!g_at_ctx.initialized || !data || len == 0) { + PPP_DEBUG_ERROR("Invalid parameters: initialized=%d, data=%p, len=%zu", + g_at_ctx.initialized, data, len); + return PPP_ERR_INVALID_PARAM; + } + + PPP_DEBUG_DEBUG("=== Sending Raw Data ==="); + PPP_DEBUG_DEBUG("Length: %zu bytes", len); + PPP_DEBUG_DEBUG("Timeout: %u ms", timeout); + + /* 直接通过 UART 发送原始数据,不添加 \r\n */ + int32_t write_result = uapi_uart_write(g_at_ctx.uart_bus, (const uint8_t *)data, len, 0); + + if (write_result != (int32_t)len) { + PPP_DEBUG_ERROR("Failed to send raw data: wrote %d bytes, expected %zu", write_result, len); + return PPP_ERR_UART; + } + + PPP_DEBUG_DEBUG("Raw data sent successfully (%zu bytes) on bus %d", len, g_at_ctx.uart_bus); + return PPP_ERR_OK; +} + +/** + * @brief 跳过开机日志 (对应chat文件中的 '' \rAT) + */ +static ppp_error_t ppp_at_skip_startup_logs(void) +{ + PPP_DEBUG_DEBUG("Skipping startup logs..."); + + // 清空接收缓冲区,跳过开机时的各种日志输出 + uint8_t dummy_buffer[256]; + uint32_t bytes_read; + + // 连续读取并丢弃开机日志,直到没有更多数据 + int retry_count = 0; + const int max_retries = 10; + + do { + ppp_error_t ret = ppp_serial_read(dummy_buffer, sizeof(dummy_buffer), &bytes_read); + if (ret != PPP_ERR_OK) { + break; + } + + if (bytes_read > 0) { + PPP_DEBUG_DEBUG("Skipped %u bytes of startup logs", bytes_read); + retry_count = 0; // 重置重试计数 + } else { + retry_count++; + } + + osal_msleep(100); + } while (retry_count < max_retries); + + PPP_DEBUG_DEBUG("Startup logs skipped"); + return PPP_ERR_OK; +} + +/* ========== PPP拨号函数已删除 ========== */ +/* N706A_NET 使用纯AT命令模式,不需要PPP拨号功能 */ +/* 如需PPP功能,请使用 ppp 组件 */ +/** + * @brief 发送AT关机指令,支持快速关机和正常关机 + * @param shutdown_type 关机类型: 0-快速关机, 1-正常流程关机 + * @return ppp_error_t + */ +ppp_error_t ppp_at_poweroff_modem(uint8_t shutdown_type) +{ + if (!g_at_ctx.initialized) { + return PPP_ERR_INVALID_PARAM; + } + + PPP_DEBUG_INFO("Sending AT+NWPWROFF=%d to power off modem", shutdown_type); + + char cmd[32]; + snprintf(cmd, sizeof(cmd), "AT+NWPWROFF=%d", shutdown_type); + + ppp_at_response_t response; + ppp_error_t ret = ppp_at_send_command(cmd, "OK", 10000, &response); + + if (ret == PPP_ERR_OK) { + PPP_DEBUG_INFO("Modem power off command sent successfully"); + } else { + PPP_DEBUG_ERROR("Failed to send modem power off command"); + } + ppp_power_off_cat1(); + return ret; +} + +/* ========== PPP挂断函数已删除 ========== */ +/* N706A_NET 使用纯AT命令模式,不需要PPP挂断功能 */ + +/** + * @brief 获取当前波特率 + */ +uint32_t ppp_at_get_current_baudrate(void) +{ + return g_at_ctx.current_baudrate; +} + +/** + * @brief 检查是否为自动波特率模式 + */ +bool ppp_at_is_auto_baudrate_mode(void) +{ + return g_at_ctx.auto_baudrate_mode; +} + +/** + * @brief 测试AT通信是否正常 + */ +ppp_error_t ppp_at_test_communication(void) +{ + PPP_DEBUG_INFO("Testing AT communication..."); + + ppp_at_response_t response; + + // 发送简单的AT命令测试通信 + ppp_error_t ret = ppp_at_send_command("AT", "OK", 1000, &response); + if (ret == PPP_ERR_OK) { + PPP_DEBUG_INFO("AT communication test passed"); + } else { + PPP_DEBUG_ERROR("AT communication test failed: %d", ret); + } + + return ret; +} + +/* 内部函数实现 */ + +/** + * @brief 检查AT响应是否完整 + * @param buffer 响应缓冲区 + * @param length 缓冲区长度 + * @return true 如果响应完整,false 否则 + */ +static bool ppp_at_check_response_complete(const char *buffer, uint32_t length) +{ + if (!buffer || length == 0) { + return false; + } + + /* 搜索OK终止符 - 修复无符号整数下溢问题 */ + if (length >= 2) { + for (uint32_t i = 0; i <= length - 2; i++) { + if (buffer[i] == 'O' && buffer[i+1] == 'K') { + /* 检查OK前面是否有换行符(避免误匹配) */ + if (i == 0 || buffer[i-1] == '\n' || buffer[i-1] == '\r') { + PPP_DEBUG_DEBUG("Found OK terminator at position %u", i); + return true; + } + } + } + } + + /* 搜索ERROR终止符 - 已经有长度检查,但确保安全 */ + if (length >= 5) { + for (uint32_t i = 0; i <= length - 5; i++) { + if (strncmp(&buffer[i], "ERROR", 5) == 0) { + /* 检查ERROR前面是否有换行符 */ + if (i == 0 || buffer[i-1] == '\n' || buffer[i-1] == '\r') { + PPP_DEBUG_DEBUG("Found ERROR terminator at position %u", i); + return true; + } + } + } + } + + /* 检查其他终止符 */ + if (strstr(buffer, "CONNECT") || + strstr(buffer, "NO CARRIER") || + strstr(buffer, "BUSY") || + strstr(buffer, "NO DIALTONE") || + strstr(buffer, "+CME ERROR") || + strstr(buffer, "+CMS ERROR")) { + PPP_DEBUG_DEBUG("Found other terminator in response"); + return true; + } + + return false; +} + +/** + * @brief 解析AT命令响应 + */ +static bool ppp_at_parse_response(const char *response, const char *expected) +{ + if (!response) { + return false; + } + + + // 检查期望响应 ,优先检测期望响应 + if (expected && strstr(response, expected)) { + return true; + } + + // 检查ABORT条件 (对应chat文件中的ABORT指令) + if (strstr(response, "BUSY") || + strstr(response, "ERROR") || + strstr(response, "NO CARRIER")) { + PPP_DEBUG_ERROR("AT command aborted: %s", response); + return false; + } + + + + return false; +} + +/** + * @brief 等待AT命令响应 - 异步版本(从环形缓冲区读取) + * + * 该函数从ppp_interrupt.c填充的环形缓冲区中异步读取AT响应数据 + * 提供同步的函数接口,但底层是异步的高速串口接收 + */ +static ppp_error_t ppp_at_wait_response(ppp_at_response_t *response, uint32_t timeout) +{ + PPP_DEBUG_DEBUG(">>> N706A AT WAIT_RESPONSE START (async mode, timeout: %u ms) <<<", timeout); + + if (!response || !g_at_response_buffer) { + PPP_DEBUG_ERROR("Invalid parameters: response=%p, buffer=%p", response, g_at_response_buffer); + return PPP_ERR_INVALID_PARAM; + } + + uint32_t start_time = (uint32_t)osal_get_jiffies(); + uint32_t bytes_received = 0; + bool response_complete = false; + + /* 清空全局缓存 */ + memset(g_at_response_buffer, 0, g_at_buffer_size); + + PPP_DEBUG_DEBUG("N706A AT: Waiting for response from async ring buffer (timeout: %u ms)", timeout); + + /* 循环从环形缓冲区读取数据直到完整响应或超时 */ + while (!response_complete) { + uint32_t current_time = (uint32_t)osal_get_jiffies(); + uint32_t elapsed = osal_jiffies_to_msecs(current_time - start_time); + + /* 检查超时 */ + if (elapsed >= timeout) { + PPP_DEBUG_WARN("N706A AT: Response timeout after %u ms", elapsed); + break; + } + + /* 等待信号量(有新数据到达),使用剩余超时时间 */ + uint32_t remaining_timeout = timeout - elapsed; + if (remaining_timeout > 100) { + remaining_timeout = 100; // 最多等100ms,然后检查一次 + } + + int sem_result = osal_sem_down_timeout(&g_at_response_sem, remaining_timeout); + if (sem_result == OSAL_SUCCESS || sem_result == OSAL_FAILURE) { + /* 有新数据到达或信号量操作失败,尝试读取环形缓冲区 */ + osal_mutex_lock(&g_at_response_mutex); + + /* 从环形缓冲区读取数据到响应缓冲区 */ + while (g_at_response_tail != g_at_response_head && + bytes_received < (g_at_buffer_size - 1)) { + g_at_response_buffer[bytes_received] = g_at_response_ring_buffer[g_at_response_tail]; + g_at_response_tail = (g_at_response_tail + 1) % N706A_AT_RESPONSE_RING_BUFFER_SIZE; + bytes_received++; + } + + osal_mutex_unlock(&g_at_response_mutex); + + /* 设置字符串结束符 */ + if (bytes_received > 0) { + g_at_response_buffer[bytes_received] = '\0'; + + PPP_DEBUG_DEBUG("N706A AT: Read %u bytes from ring buffer", bytes_received); + + /* 检查是否收到完整响应 */ + if (ppp_at_check_response_complete((char*)g_at_response_buffer, bytes_received)) { + PPP_DEBUG_DEBUG("N706A AT: Complete response detected"); + response_complete = true; + break; + } + } + } + + /* 短暂延迟避免CPU占用过高 */ + if (!response_complete) { + osal_msleep(10); + } + } + + /* 处理结果 */ + if (bytes_received > 0) { + uint32_t copy_len = (bytes_received < PPP_AT_RESPONSE_MAX_LEN - 1) ? + bytes_received : PPP_AT_RESPONSE_MAX_LEN - 1; + + memcpy_s(response->response, PPP_AT_RESPONSE_MAX_LEN, + g_at_response_buffer, copy_len); + response->response[copy_len] = '\0'; + response->length = copy_len; + + uint32_t final_time = (uint32_t)osal_get_jiffies(); + uint32_t total_elapsed = osal_jiffies_to_msecs(final_time - start_time); + + PPP_DEBUG_DEBUG("N706A AT: SUCCESS - %u bytes in %u ms", bytes_received, total_elapsed); + PPP_DEBUG_DEBUG(">>> N706A AT WAIT_RESPONSE END (SUCCESS) <<<"); + return PPP_ERR_OK; + } else { + uint32_t final_time = (uint32_t)osal_get_jiffies(); + uint32_t total_elapsed = osal_jiffies_to_msecs(final_time - start_time); + + PPP_DEBUG_WARN("N706A AT: FAILED - No response after %u ms", total_elapsed); + PPP_DEBUG_DEBUG(">>> N706A AT WAIT_RESPONSE END (FAILED) <<<"); + response->length = 0; + response->response[0] = '\0'; + return PPP_ERR_TIMEOUT; + } +} + +/** + * @brief 解析AT+CESQ响应 + * 参考N706A AT命令手册: +CESQ: ,,,,, + */ +static ppp_error_t ppp_parse_cesq_response(const char *response, n706a_network_signal_info_t *info) +{ + if (!response || !info) { + return PPP_ERR_INVALID_PARAM; + } + + memset_s(info, sizeof(n706a_network_signal_info_t), 0, sizeof(n706a_network_signal_info_t)); + + // 查找+CESQ:开头的行 + const char *cesq_line = strstr(response, "+CESQ:"); + if (!cesq_line) { + PPP_DEBUG_WARN("No +CESQ: found in response"); + return PPP_ERR_AT_FAILED; + } + + // 跳过"+CESQ:" + cesq_line += 6; + while (*cesq_line == ' ' || *cesq_line == '\t') cesq_line++; + + // +CESQ: ,,,,, + int rxlev = 99, ber = 99, rscp = 255, ecno = 255, rsrq = 255, rsrp = 255; + int parsed = sscanf(cesq_line, "%d,%d,%d,%d,%d,%d", &rxlev, &ber, &rscp, &ecno, &rsrq, &rsrp); + + if (parsed == 6) { + info->valid = true; + info->rxlev = rxlev; + info->ber = ber; + info->rscp = rscp; + info->ecno = ecno; + info->rsrq = rsrq; + info->rsrp = rsrp; + PPP_DEBUG_INFO("CESQ parsed successfully: rxlev=%d, ber=%d, rscp=%d, ecno=%d, rsrq=%d, rsrp=%d", + rxlev, ber, rscp, ecno, rsrq, rsrp); + return PPP_ERR_OK; + } else { + PPP_DEBUG_WARN("Failed to parse CESQ response, parsed: %d", parsed); + return PPP_ERR_AT_FAILED; + } +} + +/** + * @brief 获取网络信号信息 (AT+CESQ) + */ +ppp_error_t ppp_at_get_network_signal_info(n706a_network_signal_info_t *info) +{ + if (!g_at_ctx.initialized || !info) { + return PPP_ERR_INVALID_PARAM; + } + // N706A_NET模式下始终处于AT模式,无需检查 + // (PPP模式需要检查是否成功切换到AT模式) + + PPP_DEBUG_INFO("Getting network signal information (AT+CESQ)..."); + + ppp_at_response_t response; + ppp_error_t ret = ppp_at_send_command("AT+CESQ", "OK", 5000, &response); + + if (ret != PPP_ERR_OK) { + PPP_DEBUG_ERROR("AT+CESQ command failed: %d", ret); + return ret; + } + + // 解析响应 + ret = ppp_parse_cesq_response(response.response, info); + if (ret != PPP_ERR_OK) { + PPP_DEBUG_ERROR("Failed to parse CESQ response"); + return ret; + } + + // 打印详细信息 + PPP_DEBUG_INFO("=== Network Signal Information (AT+CESQ) ==="); + PPP_DEBUG_INFO("rxlev: %d", info->rxlev); + PPP_DEBUG_INFO("ber: %d", info->ber); + PPP_DEBUG_INFO("rscp: %d", info->rscp); + PPP_DEBUG_INFO("ecno: %d", info->ecno); + PPP_DEBUG_INFO("rsrq: %d", info->rsrq); + PPP_DEBUG_INFO("rsrp: %d", info->rsrp); + + // LTE信号质量评估 - 仅关注RSRQ和RSRP + PPP_DEBUG_INFO("=== LTE Signal Quality Analysis ==="); + + // RSRQ评估 (0-49有效,255无效) + if (info->rsrq == 255) { + PPP_DEBUG_INFO("RSRQ: 无法获取LTE信号质量"); + } else if (info->rsrq >= 0 && info->rsrq <= 49) { + // RSRQ转换公式: 实际值(dB) = (rsrq - 94)/10 + float rsrq_db = (info->rsrq - 94) / 10.0f; + PPP_DEBUG_INFO("RSRQ: %d (%.1f dB)", info->rsrq, rsrq_db); + + // RSRQ评估标准 (范围: -19.5dB ~ -3dB,数值越大质量越好) + if (rsrq_db >= -6.0) { + PPP_DEBUG_INFO("RSRQ评级: 极佳 (信号质量优秀)"); + } else if (rsrq_db >= -9.0) { + PPP_DEBUG_INFO("RSRQ评级: 良好 (信号质量稳定)"); + } else if (rsrq_db >= -12.0) { + PPP_DEBUG_INFO("RSRQ评级: 一般 (信号质量可用)"); + } else { + PPP_DEBUG_INFO("RSRQ评级: 较差 (信号质量不佳)"); + } + } else { + PPP_DEBUG_INFO("RSRQ: %d (超出有效范围0-49)", info->rsrq); + } + + // RSRP评估 (0-97有效,255无效) + if (info->rsrp == 255) { + PPP_DEBUG_INFO("RSRP: 无法获取LTE信号强度"); + } else if (info->rsrp >= 0 && info->rsrp <= 97) { + // RSRP转换公式: 实际值(dBm) = rsrp - 140 + float rsrp_dbm = info->rsrp - 140.0f; + PPP_DEBUG_INFO("RSRP: %d (%.0f dBm)", info->rsrp, rsrp_dbm); + + // RSRP评估标准 (范围: -140dBm ~ -43dBm,数值越大信号越强) + if (rsrp_dbm >= -80) { + PPP_DEBUG_INFO("RSRP评级: 极佳 (信号强度优秀)"); + } else if (rsrp_dbm >= -90) { + PPP_DEBUG_INFO("RSRP评级: 良好 (信号强度稳定)"); + } else if (rsrp_dbm >= -100) { + PPP_DEBUG_INFO("RSRP评级: 一般 (信号强度可用)"); + } else { + PPP_DEBUG_INFO("RSRP评级: 较差 (信号强度不足)"); + } + } else { + PPP_DEBUG_INFO("RSRP: %d (超出有效范围0-97)", info->rsrp); + } + + // 综合LTE信号质量评估 + if (info->rsrq != 255 && info->rsrp != 255 && + info->rsrq >= 0 && info->rsrq <= 49 && + info->rsrp >= 0 && info->rsrp <= 97) { + + float rsrq_db = (info->rsrq - 94) / 10.0f; + float rsrp_dbm = info->rsrp - 140.0f; + + const char *overall_quality = "未知"; + if (rsrq_db >= -6.0 && rsrp_dbm >= -80) { + overall_quality = "LTE信号极佳 (质量和强度都优秀)"; + } else if (rsrq_db >= -9.0 && rsrp_dbm >= -90) { + overall_quality = "LTE信号良好 (质量和强度都稳定)"; + } else if (rsrq_db >= -12.0 && rsrp_dbm >= -100) { + overall_quality = "LTE信号一般 (质量和强度可用)"; + } else { + overall_quality = "LTE信号较差 (需要改善网络环境)"; + } + + PPP_DEBUG_INFO("=== 综合评估: %s ===", overall_quality); + } else { + PPP_DEBUG_INFO("=== 综合评估: LTE参数无效,无法评估 ==="); + } + + PPP_DEBUG_INFO("=== End Network Signal Information ==="); + + return PPP_ERR_OK; +} + +/* ========== 导出的N706A API包装函数(用于n706a_tcp_client和n706a_net_at_cmd) ========== */ + +/** + * @brief N706A对外导出:发送AT命令并等待响应 + */ +n706a_at_error_t n706a_at_send_command(const char *command, + const char *expected_response, + uint32_t timeout, + n706a_at_response_t *response) +{ + ppp_at_response_t ppp_resp; + ppp_error_t ret = ppp_at_send_command(command, expected_response, timeout, &ppp_resp); + + if (response) { + /* 转换ppp_at_response_t到n706a_at_response_t */ + memcpy_s(response->response, sizeof(response->response), ppp_resp.response, sizeof(ppp_resp.response)); + response->length = ppp_resp.length; + response->success = ppp_resp.success; + response->error_code = (ret == PPP_ERR_OK) ? N706A_AT_OK : N706A_AT_ERR_AT_FAILED; + } + + return (ret == PPP_ERR_OK) ? N706A_AT_OK : N706A_AT_ERR_AT_FAILED; +} + +/** + * @brief N706A对外导出:测试AT通信是否正常 + */ +n706a_at_error_t n706a_at_test_communication(void) +{ + if (!g_at_ctx.initialized) { + return N706A_AT_ERR_INVALID_PARAM; + } + + n706a_at_response_t response; + n706a_at_error_t ret = n706a_at_send_command("AT", "OK", 3000, &response); + + if (ret == N706A_AT_OK && response.success) { + PPP_DEBUG_INFO("AT communication test passed\n"); + return N706A_AT_OK; + } else { + PPP_DEBUG_ERROR("AT communication test failed\n"); + return N706A_AT_ERR_AT_FAILED; + } +} + +/** + * @brief N706A对外导出:获取网络信号信息 + */ +n706a_at_error_t n706a_at_get_network_signal_info(n706a_network_signal_info_t *signal_info) +{ + if (!signal_info) { + return N706A_AT_ERR_INVALID_PARAM; + } + + /* 调用内部的ppp_at_get_network_signal_info */ + n706a_network_signal_info_t ppp_signal; + ppp_error_t ret = ppp_at_get_network_signal_info(&ppp_signal); + + if (ret == PPP_ERR_OK) { + /* 转换数据结构 */ + signal_info->rxlev = ppp_signal.rxlev; + signal_info->ber = ppp_signal.ber; + signal_info->rscp = ppp_signal.rscp; + signal_info->ecno = ppp_signal.ecno; + signal_info->rsrq = ppp_signal.rsrq; + signal_info->rsrp = ppp_signal.rsrp; + signal_info->valid = ppp_signal.valid; + return N706A_AT_OK; + } else { + signal_info->valid = false; + return N706A_AT_ERR_AT_FAILED; + } +} + +/** + * @brief N706A对外导出:设置波特率 + */ +n706a_at_error_t n706a_at_set_baudrate(uint32_t baudrate) +{ + ppp_error_t ret = ppp_at_set_baudrate(baudrate); + return (ret == PPP_ERR_OK) ? N706A_AT_OK : N706A_AT_ERR_UART; +} + +/** + * @brief N706A对外导出:获取当前波特率 + */ +uint32_t n706a_at_get_current_baudrate(void) +{ + return g_at_ctx.current_baudrate; +} + +/** + * @brief 解析 +TCPRECV URC + * @param urc_data URC数据(以 "+TCPRECV: " 开头) + * @param urc_len URC数据长度 + * @param socket_id 输出:socket ID + * @param data_len 输出:数据长度 + * @param data 输出:数据指针(指向 urc_data 内部) + * @return true=解析成功,false=失败 + * + * URC格式:+TCPRECV: ,,\r\n + * 例如:+TCPRECV: 0,10,1234567890\r\n + */ +static bool n706a_parse_tcprecv_urc(const char *urc_data, size_t urc_len, + int *socket_id, size_t *data_len, const uint8_t **data) +{ + if (!urc_data || !socket_id || !data_len || !data || urc_len < 15) { + return false; + } + + // 跳过 "+TCPRECV: " + const char *p = urc_data + 10; + const char *end = urc_data + urc_len; + + // 解析 socket_id + *socket_id = 0; + while (p < end && *p >= '0' && *p <= '9') { + *socket_id = *socket_id * 10 + (*p - '0'); + p++; + } + + if (p >= end || *p != ',') { + PPP_DEBUG_ERROR("URC parse error: no comma after socket_id"); + return false; + } + p++; // 跳过逗号 + + // 解析 length + *data_len = 0; + while (p < end && *p >= '0' && *p <= '9') { + *data_len = *data_len * 10 + (*p - '0'); + p++; + } + + if (p >= end || *p != ',') { + PPP_DEBUG_ERROR("URC parse error: no comma after length"); + return false; + } + p++; // 跳过逗号 + + // 数据起始位置 + *data = (const uint8_t *)p; + + // 验证数据长度是否合理 + size_t remaining = end - p; + if (*data_len > remaining) { + PPP_DEBUG_WARN("URC data_len=%u exceeds remaining=%u, truncating", + (unsigned int)*data_len, (unsigned int)remaining); + *data_len = remaining; + + // 移除尾部的 \r\n(如果存在) + if (*data_len >= 2 && (*data)[*data_len - 2] == '\r' && (*data)[*data_len - 1] == '\n') { + *data_len -= 2; + } + } + + PPP_DEBUG_DEBUG("Parsed URC: socket=%d, len=%u", *socket_id, (unsigned int)*data_len); + return true; +} + +/** + * @brief 注册TCP URC接收回调 + * @param callback 回调函数指针,NULL表示取消注册 + */ +void n706a_at_register_tcp_urc_callback(n706a_tcp_urc_callback_t callback) +{ + g_tcp_urc_callback = callback; + PPP_DEBUG_INFO("TCP URC callback %s", callback ? "registered" : "unregistered"); +} \ No newline at end of file diff --git a/code/sdk/zhihui/n706a_net/src/n706a_http_client.c b/code/sdk/zhihui/n706a_net/src/n706a_http_client.c index 649e370af..d289d5958 100644 --- a/code/sdk/zhihui/n706a_net/src/n706a_http_client.c +++ b/code/sdk/zhihui/n706a_net/src/n706a_http_client.c @@ -118,13 +118,18 @@ n706a_httpc_result_t n706a_http_client_get(n706a_http_client_t *client, return N706A_HTTP_ECONN; } - /* 构造GET请求 */ - char request_buf[2048]; + /* 动态分配请求缓冲区 */ + char *request_buf = (char *)malloc(2048); + if (!request_buf) { + return N706A_HTTP_ENOMEM; + } + int request_len = http_build_request(client, "GET", path, NULL, 0, NULL, - request_buf, sizeof(request_buf)); + request_buf, 2048); if (request_len < 0) { + free(request_buf); return N706A_HTTP_ENOBUFS; } @@ -135,28 +140,37 @@ n706a_httpc_result_t n706a_http_client_get(n706a_http_client_t *client, request_len, &sent); + free(request_buf); + if (ret != N706A_TCP_SUCCESS || sent != (size_t)request_len) { return N706A_HTTP_ESEND; } - /* 接收响应 */ - uint8_t response_buf[8192]; - size_t received; + /* 动态分配响应缓冲区 */ + uint8_t *response_buf = (uint8_t *)malloc(8192); + if (!response_buf) { + return N706A_HTTP_ENOMEM; + } + size_t received; ret = n706a_tcp_recv(&client->tcp_sock, response_buf, - sizeof(response_buf) - 1, + 8192 - 1, &received, client->timeout_ms); if (ret != N706A_TCP_SUCCESS) { + free(response_buf); return N706A_HTTP_ERECV; } response_buf[received] = '\0'; /* 解析响应 */ - return http_parse_response((char*)response_buf, received, client, client_data); + n706a_httpc_result_t result = http_parse_response((char*)response_buf, received, client, client_data); + free(response_buf); + + return result; } /** @@ -177,13 +191,18 @@ n706a_httpc_result_t n706a_http_client_post(n706a_http_client_t *client, return N706A_HTTP_ECONN; } - /* 构造POST请求 */ - char request_buf[4096]; + /* 动态分配请求缓冲区 */ + char *request_buf = (char *)malloc(4096); + if (!request_buf) { + return N706A_HTTP_ENOMEM; + } + int request_len = http_build_request(client, "POST", path, body, body_len, content_type, - request_buf, sizeof(request_buf)); + request_buf, 4096); if (request_len < 0) { + free(request_buf); return N706A_HTTP_ENOBUFS; } @@ -194,27 +213,36 @@ n706a_httpc_result_t n706a_http_client_post(n706a_http_client_t *client, request_len, &sent); + free(request_buf); + if (ret != N706A_TCP_SUCCESS) { return N706A_HTTP_ESEND; } - /* 接收响应 */ - uint8_t response_buf[8192]; - size_t received; + /* 动态分配响应缓冲区 */ + uint8_t *response_buf = (uint8_t *)malloc(8192); + if (!response_buf) { + return N706A_HTTP_ENOMEM; + } + size_t received; ret = n706a_tcp_recv(&client->tcp_sock, response_buf, - sizeof(response_buf) - 1, + 8192 - 1, &received, client->timeout_ms); if (ret != N706A_TCP_SUCCESS) { + free(response_buf); return N706A_HTTP_ERECV; } response_buf[received] = '\0'; - return http_parse_response((char*)response_buf, received, client, client_data); + n706a_httpc_result_t result = http_parse_response((char*)response_buf, received, client, client_data); + free(response_buf); + + return result; } /** @@ -235,26 +263,41 @@ n706a_httpc_result_t n706a_http_client_put(n706a_http_client_t *client, return N706A_HTTP_ECONN; } - char request_buf[4096]; + /* 动态分配请求缓冲区 */ + char *request_buf = (char *)malloc(4096); + if (!request_buf) { + return N706A_HTTP_ENOMEM; + } + int request_len = http_build_request(client, "PUT", path, body, body_len, content_type, - request_buf, sizeof(request_buf)); + request_buf, 4096); if (request_len < 0) { + free(request_buf); return N706A_HTTP_ENOBUFS; } size_t sent; n706a_tcp_send(&client->tcp_sock, (uint8_t*)request_buf, request_len, &sent); + free(request_buf); + + /* 动态分配响应缓冲区 */ + uint8_t *response_buf = (uint8_t *)malloc(8192); + if (!response_buf) { + return N706A_HTTP_ENOMEM; + } - uint8_t response_buf[8192]; size_t received; - n706a_tcp_recv(&client->tcp_sock, response_buf, sizeof(response_buf) - 1, + n706a_tcp_recv(&client->tcp_sock, response_buf, 8192 - 1, &received, client->timeout_ms); response_buf[received] = '\0'; - return http_parse_response((char*)response_buf, received, client, client_data); + n706a_httpc_result_t result = http_parse_response((char*)response_buf, received, client, client_data); + free(response_buf); + + return result; } /** @@ -272,26 +315,41 @@ n706a_httpc_result_t n706a_http_client_delete(n706a_http_client_t *client, return N706A_HTTP_ECONN; } - char request_buf[2048]; + /* 动态分配请求缓冲区 */ + char *request_buf = (char *)malloc(2048); + if (!request_buf) { + return N706A_HTTP_ENOMEM; + } + int request_len = http_build_request(client, "DELETE", path, NULL, 0, NULL, - request_buf, sizeof(request_buf)); + request_buf, 2048); if (request_len < 0) { + free(request_buf); return N706A_HTTP_ENOBUFS; } size_t sent; n706a_tcp_send(&client->tcp_sock, (uint8_t*)request_buf, request_len, &sent); + free(request_buf); + + /* 动态分配响应缓冲区 */ + uint8_t *response_buf = (uint8_t *)malloc(8192); + if (!response_buf) { + return N706A_HTTP_ENOMEM; + } - uint8_t response_buf[8192]; size_t received; - n706a_tcp_recv(&client->tcp_sock, response_buf, sizeof(response_buf) - 1, + n706a_tcp_recv(&client->tcp_sock, response_buf, 8192 - 1, &received, client->timeout_ms); response_buf[received] = '\0'; - return http_parse_response((char*)response_buf, received, client, client_data); + n706a_httpc_result_t result = http_parse_response((char*)response_buf, received, client, client_data); + free(response_buf); + + return result; } /** @@ -309,26 +367,41 @@ n706a_httpc_result_t n706a_http_client_head(n706a_http_client_t *client, return N706A_HTTP_ECONN; } - char request_buf[2048]; + /* 动态分配请求缓冲区 */ + char *request_buf = (char *)malloc(2048); + if (!request_buf) { + return N706A_HTTP_ENOMEM; + } + int request_len = http_build_request(client, "HEAD", path, NULL, 0, NULL, - request_buf, sizeof(request_buf)); + request_buf, 2048); if (request_len < 0) { + free(request_buf); return N706A_HTTP_ENOBUFS; } size_t sent; n706a_tcp_send(&client->tcp_sock, (uint8_t*)request_buf, request_len, &sent); + free(request_buf); + + /* 动态分配响应缓冲区 */ + uint8_t *response_buf = (uint8_t *)malloc(8192); + if (!response_buf) { + return N706A_HTTP_ENOMEM; + } - uint8_t response_buf[8192]; size_t received; - n706a_tcp_recv(&client->tcp_sock, response_buf, sizeof(response_buf) - 1, + n706a_tcp_recv(&client->tcp_sock, response_buf, 8192 - 1, &received, client->timeout_ms); response_buf[received] = '\0'; - return http_parse_response((char*)response_buf, received, client, client_data); + n706a_httpc_result_t result = http_parse_response((char*)response_buf, received, client, client_data); + free(response_buf); + + return result; } /** diff --git a/code/sdk/zhihui/n706a_net/src/n706a_net_api.c b/code/sdk/zhihui/n706a_net/src/n706a_net_api.c new file mode 100644 index 000000000..e72adf458 --- /dev/null +++ b/code/sdk/zhihui/n706a_net/src/n706a_net_api.c @@ -0,0 +1,908 @@ +/** + * @file n706a_net_api.c + * @brief N706A AT命令网络API实现(独立于PPP) + */ + +#include "n706a_net_api.h" +#include "n706a_at_handler.h" +#include "n706a_tcp_client.h" +#include "ppp_config.h" +#include "ppp_debug.h" +#include "ppp_power_ctl.h" +#include "ppp_serial.h" +#include "ppp_cat1_api.h" +#include "ppp_interrupt.h" +#include "ppp_api.h" +#include "soc_osal.h" +#include "osal_debug.h" +#include "schedule/osal_task.h" +#include "securec.h" +#include +#include + +// N706A网络状态 +static n706a_net_state_t g_n706a_state = N706A_NET_STATE_IDLE; + +// N706A配置参数 +static n706a_net_config_t g_n706a_config = {0}; + +// 4G电源初始化状态 +static bool g_4g_power_initialized = false; + +// 外部声明cat1_api_enable_cat1函数 +extern void cat1_api_enable_cat1(bool isopen); + +/** + * @brief 初始化4G电源控制 + */ +errcode_t n706a_net_power_init(void) +{ + if (g_4g_power_initialized) { + PPP_DEBUG_PRINT("4G power already initialized\n"); + return ERRCODE_SUCC; + } + + // 1. 配置4G电源控制引脚 + PPP_DEBUG_PRINT("Configuring N706A power pins\n"); + + ppp_ctl_pin_4g_t pin_config = { + .bat_pin = S_AGPIO_L18, + .bat_pin_en_value = 1, + .pwr_pin = S_AGPIO_L23, + .pwr_pin_en_value = 1, + .reset_pin = S_AGPIO_L24, + .reset_pin_en_value = 1, + .ring_pin = S_MGPIO21, // CTS cat1 wakeup mcu + .ring_pin_en_value = 1, + .wakemodem_pin = S_MGPIO22, // RTS mcu wakeup cat1 + .wakemodem_pin_en_value = 0 + }; + + // 注册4G电源控制引脚配置 + ppp_register_ctl_pin(&pin_config); + + PPP_DEBUG_PRINT("Initializing GPIO system\n"); + ppp_api_gpio_init(); + + PPP_DEBUG_PRINT("4G power control pins registered successfully\n"); + g_4g_power_initialized = true; + return ERRCODE_SUCC; +} + +/** + * @brief 控制4G电源开关 + */ +errcode_t n706a_net_power_control(bool is_open) +{ + if (is_open) { + // 启用4G电源 + PPP_DEBUG_PRINT("Enabling N706A module power...\n"); + cat1_api_enable_cat1(true); + osal_msleep(500); + + // 唤醒4G模块 + PPP_DEBUG_PRINT("Waking up N706A module...\n"); + cat1_api_wakeup_cat1(true); // true = 唤醒 + PPP_DEBUG_PRINT("N706A module power enabled\n"); + } else { + // 关闭4G电源 + PPP_DEBUG_PRINT("Disabling N706A module power...\n"); + cat1_api_enable_cat1(false); + PPP_DEBUG_PRINT("N706A module power disabled\n"); + } + + return ERRCODE_SUCC; +} + +/** + * @brief 检查4G电源是否已初始化 + */ +bool n706a_net_is_power_initialized(void) +{ + return g_4g_power_initialized; +} + +/** + * @brief 初始化N706A AT网络系统 + */ +errcode_t n706a_net_init(const n706a_net_config_t *config) +{ + if (!config) { + PPP_DEBUG_ERROR("Invalid config parameter\n"); + return ERRCODE_FAIL; + } + + if (g_n706a_state != N706A_NET_STATE_IDLE) { + PPP_DEBUG_WARN("N706A network already initialized\n"); + return ERRCODE_SUCC; + } + + // 保存配置 + memcpy_s(&g_n706a_config, sizeof(n706a_net_config_t), + config, sizeof(n706a_net_config_t)); + + PPP_DEBUG_PRINT("=== N706A AT Network Initialization Start ===\n"); + + // 1. 初始化4G电源控制(如果还没初始化) + if (!g_4g_power_initialized) { + PPP_DEBUG_PRINT("Step 1: Initializing 4G power control\n"); + errcode_t ret = n706a_net_power_init(); + if (ret != ERRCODE_SUCC) { + PPP_DEBUG_ERROR("Failed to initialize 4G power control\n"); + return ret; + } + } + + // 2. 打开4G电源 + PPP_DEBUG_PRINT("Step 2: Powering on N706A module\n"); + errcode_t ret = n706a_net_power_control(true); + if (ret != ERRCODE_SUCC) { + PPP_DEBUG_ERROR("Failed to power on N706A module\n"); + return ret; + } + + // 3. 初始化串口 + PPP_DEBUG_PRINT("Step 3: Initializing UART (baudrate: %u)\n", config->baudrate); + ppp_serial_config_t serial_config = { + .uart_bus = SIO_UART_BUS, + .baudrate = config->baudrate, + .rx_buffer_size = 4096, // 4KB接收缓冲区(用于高速数据接收) + .tx_buffer_size = 2048, // 2KB发送缓冲区 + .rx_callback = NULL, // N706A使用中断方式,不需要回调 + .rx_callback_arg = NULL + }; + ret = ppp_serial_init(&serial_config); + if (ret != PPP_ERR_OK) { + PPP_DEBUG_ERROR("Failed to initialize UART\n"); + n706a_net_power_control(false); + return ERRCODE_FAIL; + } + + // 4. 初始化AT命令处理器 + PPP_DEBUG_PRINT("Step 4: Initializing AT command handler\n"); + ret = ppp_at_handler_init(SIO_UART_BUS, config->baudrate, config->auto_baudrate); + if (ret != PPP_ERR_OK) { + PPP_DEBUG_ERROR("Failed to initialize AT handler\n"); + ppp_serial_deinit(); + n706a_net_power_control(false); + return ERRCODE_FAIL; + } + + // 5. 清空串口缓冲区(N706A_NET始终处于AT模式,无需切换) + PPP_DEBUG_PRINT("Step 5: Clearing UART buffer (always in AT mode)\n"); + ppp_serial_flush_rx(); // 清理启动时可能残留的数据 + + // 6. 启动中断接收系统 + PPP_DEBUG_PRINT("Step 6: Enabling interrupt reception\n"); + ret = ppp_interrupt_enable(); + if (ret != PPP_ERR_OK) { + PPP_DEBUG_ERROR("Failed to enable interrupt reception\n"); + ppp_at_handler_deinit(); + ppp_serial_deinit(); + n706a_net_power_control(false); + return ERRCODE_FAIL; + } + + // 7. 配置TCP接收模式为直接输出(URC模式) + PPP_DEBUG_PRINT("Step 7: Configuring TCP URC mode\n"); + n706a_at_response_t at_resp; + n706a_at_error_t at_ret = n706a_at_send_command("AT+RECVMODE=1", "OK", 3000, &at_resp); + if (at_ret != N706A_AT_OK) { + PPP_DEBUG_WARN("Failed to set RECVMODE=1, using default\n"); + } + PPP_DEBUG_INFO("response: %d , %s\n",at_resp.length,at_resp.response); + // 8. 注册TCP URC接收回调 + PPP_DEBUG_PRINT("Step 8: Registering TCP URC callback\n"); + n706a_at_register_tcp_urc_callback(n706a_tcp_on_urc_received); + + // 9. 检查GPRS附着状态(AT+CGATT?) + PPP_DEBUG_PRINT("Step 9: Checking GPRS attachment status (AT+CGATT?)\n"); + at_ret = n706a_at_send_command("AT+CGATT?", "OK", 5000, &at_resp); + if (at_ret != N706A_AT_OK) { + PPP_DEBUG_ERROR("Failed to query GPRS attachment status\n"); + goto cleanup; + } + + // 解析+CGATT响应 (期望: "+CGATT: 1") + bool gprs_attached = false; + if (at_resp.length > 0 && strstr(at_resp.response, "+CGATT: 1")) { + gprs_attached = true; + PPP_DEBUG_PRINT("GPRS already attached\n"); + } else { + PPP_DEBUG_WARN("GPRS not attached, attempting manual attach (AT+CGATT=1)\n"); + + // 10. 手动附着GPRS(AT+CGATT=1) + at_ret = n706a_at_send_command("AT+CGATT=1", "OK", 30000, &at_resp); // 30s超时 + if (at_ret != N706A_AT_OK) { + PPP_DEBUG_ERROR("Failed to attach GPRS, cannot proceed\n"); + PPP_DEBUG_ERROR("Response: %s\n", at_resp.length > 0 ? at_resp.response : "NULL"); + goto cleanup; + } + + PPP_DEBUG_PRINT("GPRS attached successfully\n"); + gprs_attached = true; + + // 等待附着稳定 + osal_msleep(1000); + } + + // 11. 激活网络连接(AT+XIIC=1) + PPP_DEBUG_PRINT("Step 11: Activating PDP context (AT+XIIC=1)\n"); + at_ret = n706a_at_send_command("AT+XIIC=1", "OK", 15000, &at_resp); // 增加超时至15s + if (at_ret != N706A_AT_OK) { + PPP_DEBUG_ERROR("Failed to activate PDP context (AT+XIIC=1)\n"); + PPP_DEBUG_ERROR("Response: %s\n", at_resp.length > 0 ? at_resp.response : "NULL"); + PPP_DEBUG_ERROR("Possible causes:"); + PPP_DEBUG_ERROR(" 1. SIM card not detected or locked"); + PPP_DEBUG_ERROR(" 2. Network registration failed (check AT+CREG?)"); + PPP_DEBUG_ERROR(" 3. APN configuration error"); + PPP_DEBUG_ERROR(" 4. Operator network issue"); + goto cleanup; + } + + PPP_DEBUG_PRINT("PDP context activated successfully\n"); + PPP_DEBUG_INFO("response: %d , %s\n",at_resp.length,at_resp.response); + + g_n706a_state = N706A_NET_STATE_READY; + PPP_DEBUG_PRINT("=== N706A AT Network Initialized Successfully ===\n"); + PPP_DEBUG_PRINT("Mode: Pure AT commands (no PPP dialing)\n"); + PPP_DEBUG_PRINT("Reception: Asynchronous interrupt + URC push\n"); + PPP_DEBUG_PRINT("Network: PDP context activated\n"); + PPP_DEBUG_PRINT("GPRS: Attached and ready\n"); + + return ERRCODE_SUCC; + +cleanup: + PPP_DEBUG_ERROR("=== N706A AT Network Initialization Failed ===\n"); + n706a_net_deinit(); + return ERRCODE_FAIL; +} + +/** + * @brief 反初始化N706A AT网络系统 + */ +errcode_t n706a_net_deinit(void) +{ + if (g_n706a_state == N706A_NET_STATE_IDLE) { + PPP_DEBUG_WARN("N706A network not initialized\n"); + return ERRCODE_SUCC; + } + + PPP_DEBUG_PRINT("=== N706A AT Network Deinitialization Start ===\n"); + + // 1. 停止中断接收 + PPP_DEBUG_PRINT("Step 1: Disabling interrupt reception\n"); + ppp_interrupt_disable(); + + // 2. 反初始化AT处理器 + PPP_DEBUG_PRINT("Step 2: Deinitializing AT handler\n"); + ppp_at_handler_deinit(); + + // 3. 反初始化串口 + PPP_DEBUG_PRINT("Step 3: Deinitializing UART\n"); + ppp_serial_deinit(); + + // 4. 关闭4G电源 + PPP_DEBUG_PRINT("Step 4: Powering off N706A module\n"); + n706a_net_power_control(false); + + g_n706a_state = N706A_NET_STATE_IDLE; + PPP_DEBUG_PRINT("=== N706A AT Network Deinitialized ===\n"); + + return ERRCODE_SUCC; +} + +/** + * @brief 获取N706A网络状态 + */ +n706a_net_state_t n706a_net_get_state(void) +{ + return g_n706a_state; +} + +/** + * @brief 获取并显示网络信号信息 + * @return 错误码 + */ +errcode_t n706a_get_network_signal_info(void) +{ + if (g_n706a_state == N706A_NET_STATE_IDLE) { + PPP_DEBUG_ERROR("N706A not initialized"); + return ERRCODE_FAIL; + } + + PPP_DEBUG_INFO("Getting network signal information..."); + + n706a_network_signal_info_t signal_info; + n706a_at_error_t ret = n706a_at_get_network_signal_info(&signal_info); + + if (ret != N706A_AT_OK) { + PPP_DEBUG_ERROR("Failed to get network signal information: %d", ret); + return ERRCODE_FAIL; + } + + if (!signal_info.valid) { + PPP_DEBUG_WARN("Network signal information is not valid"); + return ERRCODE_FAIL; + } + + // 输出格式化的信号信息摘要 + PPP_DEBUG_CRITICAL("=== Network Signal Summary (AT+CESQ) ==="); + + // 仅关注LTE关键参数: RSRQ和RSRP + if (signal_info.rsrq != 255 && signal_info.rsrp != 255 && + signal_info.rsrq >= 0 && signal_info.rsrq <= 49 && + signal_info.rsrp >= 0 && signal_info.rsrp <= 97) { + + // CESQ格式的RSRQ和RSRP转换 (使用正确公式) + float rsrq_db = (signal_info.rsrq - 94) / 10.0f; // (rsrq - 94)/10 + float rsrp_dbm = signal_info.rsrp - 140.0f; // rsrp - 140 + + PPP_DEBUG_CRITICAL("LTE Signal Quality:"); + PPP_DEBUG_CRITICAL(" RSRQ: %d (%.1f dB)", signal_info.rsrq, rsrq_db); + PPP_DEBUG_CRITICAL(" RSRP: %d (%.0f dBm)", signal_info.rsrp, rsrp_dbm); + + // 综合质量评估 (基于RSRQ和RSRP) + const char *overall_quality = "未知"; + if (rsrq_db >= -6.0 && rsrp_dbm >= -80) { + overall_quality = "LTE信号极佳 (质量和强度都优秀)"; + } else if (rsrq_db >= -9.0 && rsrp_dbm >= -90) { + overall_quality = "LTE信号良好 (质量和强度都稳定)"; + } else if (rsrq_db >= -12.0 && rsrp_dbm >= -100) { + overall_quality = "LTE信号一般 (质量和强度可用)"; + } else { + overall_quality = "LTE信号较差 (需要改善网络环境)"; + } + + PPP_DEBUG_CRITICAL("Overall LTE Quality: %s", overall_quality); + } else { + PPP_DEBUG_CRITICAL("LTE参数无效,无法评估信号质量"); + PPP_DEBUG_CRITICAL("RSRQ: %d, RSRP: %d", signal_info.rsrq, signal_info.rsrp); + } + + PPP_DEBUG_CRITICAL("=== End Signal Summary ==="); + + return ERRCODE_SUCC; +} + +/** + * @brief 启用4G模块电源省电模式 + * @return ERRCODE_SUCC-成功,其他-失败 + * + * 功能说明: + * - 执行 AT+NWENPWRSAVE=1 命令 + * - 配置为:低电平唤醒,高电平进入休眠 + * - N706A_NET始终在AT模式 + */ +errcode_t n706a_enable_power_save_mode(void) +{ + if (g_n706a_state == N706A_NET_STATE_IDLE) { + PPP_DEBUG_ERROR("N706A not initialized"); + return ERRCODE_FAIL; + } + + PPP_DEBUG_INFO("Enabling power save mode (AT+NWENPWRSAVE=1)..."); + + n706a_at_response_t response; + n706a_at_error_t ret = n706a_at_send_command("AT+NWENPWRSAVE=1", "OK", 5000, &response); + + if (ret != N706A_AT_OK) { + PPP_DEBUG_ERROR("Failed to send AT+NWENPWRSAVE command"); + return ERRCODE_FAIL; + } + + if (!response.success) { + PPP_DEBUG_ERROR("AT+NWENPWRSAVE command failed, response: %s", response.response); + return ERRCODE_FAIL; + } + + PPP_DEBUG_CRITICAL("Power save mode enabled successfully"); + PPP_DEBUG_INFO(" Mode: High level wakes up, Low level enters sleep"); + + return ERRCODE_SUCC; +} + +/* ========== Runtime Status Update Helper Functions ========== */ + +/** + * @brief 解析AT+CGSN响应获取IMEI号 + */ +static errcode_t n706a_parse_imei_response(const char *response, char *imei, size_t imei_size) +{ + if (!response || !imei || imei_size == 0) { + return ERRCODE_INVALID_PARAM; + } + + const char *start = strstr(response, "+CGSN: "); + if (start == NULL) { + return ERRCODE_FAIL; + } + + start += 7; // 跳过"+CGSN: " + const char *end = strstr(start, "\r"); + if (end == NULL) { + end = strstr(start, "\n"); + } + if (end == NULL) { + return ERRCODE_FAIL; + } + + size_t len = end - start; + if (len >= imei_size) { + len = imei_size - 1; + } + + strncpy_s(imei, imei_size, start, len); + imei[len] = '\0'; + + return ERRCODE_SUCC; +} + +/** + * @brief 解析AT+CSQ响应获取信号强度 + */ +static errcode_t n706a_parse_signal_response(const char *response, int *signal) +{ + if (!response || !signal) { + return ERRCODE_INVALID_PARAM; + } + + const char *start = strstr(response, "+CSQ: "); + if (start == NULL) { + return ERRCODE_FAIL; + } + + start += 6; // 跳过"+CSQ: " + int sig, ber; + if (sscanf_s(start, "%d,%d", &sig, &ber) == 2) { + *signal = sig; + return ERRCODE_SUCC; + } + + return ERRCODE_FAIL; +} + +/** + * @brief 解析AT+COPS响应获取运营商信息 + */ +static errcode_t n706a_parse_operator_response(const char *response, char *operator, size_t operator_size) +{ + if (!response || !operator || operator_size == 0) { + return ERRCODE_INVALID_PARAM; + } + + const char *start = strstr(response, "+COPS: "); + if (start == NULL) { + return ERRCODE_FAIL; + } + + // 查找第一个引号 + const char *quote_start = strchr(start, '"'); + if (quote_start == NULL) { + return ERRCODE_FAIL; + } + quote_start++; // 跳过引号 + + // 查找第二个引号 + const char *quote_end = strchr(quote_start, '"'); + if (quote_end == NULL) { + return ERRCODE_FAIL; + } + + size_t len = quote_end - quote_start; + if (len >= operator_size) { + len = operator_size - 1; + } + + strncpy_s(operator, operator_size, quote_start, len); + operator[len] = '\0'; + + return ERRCODE_SUCC; +} + +/** + * @brief 解析AT+CCID响应获取ICCID + */ +static errcode_t n706a_parse_iccid_response(const char *response, char *iccid, size_t iccid_size) +{ + if (!response || !iccid || iccid_size == 0) { + return ERRCODE_INVALID_PARAM; + } + + const char *start = strstr(response, "+CCID: "); + if (start == NULL) { + return ERRCODE_FAIL; + } + + start += 7; // 跳过"+CCID: " + const char *end = strstr(start, "\r"); + if (end == NULL) { + end = strstr(start, "\n"); + } + if (end == NULL) { + return ERRCODE_FAIL; + } + + size_t len = end - start; + if (len >= iccid_size) { + len = iccid_size - 1; + } + + strncpy_s(iccid, iccid_size, start, len); + iccid[len] = '\0'; + + return ERRCODE_SUCC; +} + +/** + * @brief 解析AT+GMR响应获取版本信息 + */ +static errcode_t n706a_parse_version_response(const char *response, char *version, size_t version_size) +{ + if (!response || !version || version_size == 0) { + return ERRCODE_INVALID_PARAM; + } + + const char *start = strstr(response, "+GMR: "); + if (start == NULL) { + return ERRCODE_FAIL; + } + + start += 6; // 跳过"+GMR: " + const char *end = strstr(start, "\r"); + if (end == NULL) { + end = strstr(start, "\n"); + } + if (end == NULL) { + return ERRCODE_FAIL; + } + + size_t len = end - start; + if (len >= version_size) { + len = version_size - 1; + } + + strncpy_s(version, version_size, start, len); + version[len] = '\0'; + + return ERRCODE_SUCC; +} + +/** + * @brief 解析AT+CREG或AT+CEREG响应获取网络注册状态 + */ +static errcode_t n706a_parse_network_status_response(const char *response, uint8_t *network_status) +{ + if (!response || !network_status) { + return ERRCODE_INVALID_PARAM; + } + + const char *start = strstr(response, "+CREG: "); + if (start == NULL) { + start = strstr(response, "+CEREG: "); + if (start == NULL) { + return ERRCODE_FAIL; + } + start += 8; // 跳过"+CEREG: " + } else { + start += 7; // 跳过"+CREG: " + } + + int n, stat; + if (sscanf_s(start, "%d,%d", &n, &stat) == 2) { + *network_status = (uint8_t)stat; + return ERRCODE_SUCC; + } + + return ERRCODE_FAIL; +} + +/* ========== Runtime Status Update Functions ========== */ + +/** + * @brief 通过AT命令获取IMEI号并更新runtime状态 + */ +static errcode_t n706a_api_update_imei(void) +{ + char imei_temp[32] = {0}; + n706a_at_response_t response; + + // 发送AT+CGSN命令 + n706a_at_error_t ret = n706a_at_send_command("AT+CGSN", "OK", 1000, &response); + if (ret != N706A_AT_OK) { + PPP_DEBUG_ERROR("Failed to send AT+CGSN command"); + return ERRCODE_FAIL; + } + + if (n706a_parse_imei_response(response.response, imei_temp, sizeof(imei_temp)) != ERRCODE_SUCC) { + PPP_DEBUG_ERROR("Failed to parse IMEI response"); + return ERRCODE_FAIL; + } + + // 更新runtime状态 + cat1_api_runtime_set_cat1_imei(imei_temp); + PPP_DEBUG_CRITICAL("Updated IMEI: %s", imei_temp); + + return ERRCODE_SUCC; +} + +/** + * @brief 通过AT命令获取信号强度并更新runtime状态 + */ +static errcode_t n706a_api_update_signal(void) +{ + int signal = 99; // 默认无信号 + n706a_at_response_t response; + + // 发送AT+CSQ命令 + n706a_at_error_t ret = n706a_at_send_command("AT+CSQ", "OK", 1000, &response); + if (ret != N706A_AT_OK) { + PPP_DEBUG_ERROR("Failed to send AT+CSQ command"); + return ERRCODE_FAIL; + } + + if (n706a_parse_signal_response(response.response, &signal) != ERRCODE_SUCC) { + PPP_DEBUG_ERROR("Failed to parse signal response"); + return ERRCODE_FAIL; + } + + // 更新runtime状态 + cat1_api_runtime_set_cat1_signal(signal); + PPP_DEBUG_CRITICAL("Updated signal strength: %d", signal); + + return ERRCODE_SUCC; +} + +/** + * @brief 通过AT命令获取运营商信息并更新runtime状态 + */ +static errcode_t n706a_api_update_operator(void) +{ + char operator_temp[64] = {0}; + n706a_at_response_t response; + + // 发送AT+COPS?命令 + n706a_at_error_t ret = n706a_at_send_command("AT+COPS?", "OK", 10000, &response); + if (ret != N706A_AT_OK) { + PPP_DEBUG_ERROR("Failed to send AT+COPS command"); + return ERRCODE_FAIL; + } + + if (n706a_parse_operator_response(response.response, operator_temp, sizeof(operator_temp)) != ERRCODE_SUCC) { + PPP_DEBUG_ERROR("Failed to parse operator response"); + return ERRCODE_FAIL; + } + + // 更新runtime状态 + cat1_api_runtime_set_cat1_operator(operator_temp); + PPP_DEBUG_CRITICAL("Updated operator: %s", operator_temp); + + return ERRCODE_SUCC; +} + +/** + * @brief 通过AT命令获取ICCID并更新runtime状态 + */ +static errcode_t n706a_api_update_iccid(void) +{ + char iccid_temp[32] = {0}; + n706a_at_response_t response; + + // 发送AT+CCID命令 + n706a_at_error_t ret = n706a_at_send_command("AT+CCID", "OK", 5000, &response); + if (ret != N706A_AT_OK) { + PPP_DEBUG_ERROR("Failed to send AT+CCID command"); + return ERRCODE_FAIL; + } + + if (n706a_parse_iccid_response(response.response, iccid_temp, sizeof(iccid_temp)) != ERRCODE_SUCC) { + PPP_DEBUG_ERROR("Failed to parse ICCID response"); + return ERRCODE_FAIL; + } + + // 更新runtime状态 + cat1_api_runtime_set_cat1_iccid(iccid_temp); + PPP_DEBUG_CRITICAL("Updated ICCID: %s", iccid_temp); + + return ERRCODE_SUCC; +} + +/** + * @brief 通过AT命令获取版本信息并更新runtime状态 + */ +static errcode_t n706a_api_update_version(void) +{ + char version_temp[64] = {0}; + n706a_at_response_t response; + + // 发送AT+GMR命令 + n706a_at_error_t ret = n706a_at_send_command("AT+GMR", "OK", 5000, &response); + if (ret != N706A_AT_OK) { + PPP_DEBUG_ERROR("Failed to send AT+GMR command"); + return ERRCODE_FAIL; + } + + if (n706a_parse_version_response(response.response, version_temp, sizeof(version_temp)) != ERRCODE_SUCC) { + PPP_DEBUG_ERROR("Failed to parse version response"); + return ERRCODE_FAIL; + } + + // 更新runtime状态 + cat1_api_runtime_set_cat1_modemver(version_temp); + PPP_DEBUG_CRITICAL("Updated modem version: %s", version_temp); + + return ERRCODE_SUCC; +} + +/** + * @brief 通过AT命令获取网络注册状态并更新runtime状态 + */ +static errcode_t n706a_api_update_network_status(void) +{ + uint8_t network_status = 0; + n706a_at_response_t response; + errcode_t ret; + + // 先尝试AT+CREG + n706a_at_error_t at_ret = n706a_at_send_command("AT+CREG?", "OK", 5000, &response); + if (at_ret == N706A_AT_OK) { + ret = n706a_parse_network_status_response(response.response, &network_status); + if (ret == ERRCODE_SUCC) { + // 根据网络注册状态设置网络类型 + cat1_api_runtime_set_cat1_networktype(network_status); + PPP_DEBUG_CRITICAL("Updated network status (CREG): %d", network_status); + return ERRCODE_SUCC; + } + } + + // 如果CREG失败,尝试AT+CEREG + at_ret = n706a_at_send_command("AT+CEREG?", "OK", 5000, &response); + if (at_ret != N706A_AT_OK) { + PPP_DEBUG_ERROR("Failed to send AT+CEREG command"); + return ERRCODE_FAIL; + } + + ret = n706a_parse_network_status_response(response.response, &network_status); + if (ret != ERRCODE_SUCC) { + PPP_DEBUG_ERROR("Failed to parse network status response"); + return ret; + } + + // 根据网络注册状态设置网络类型 + cat1_api_runtime_set_cat1_networktype(network_status); + PPP_DEBUG_CRITICAL("Updated network status (CEREG): %d", network_status); + + return ERRCODE_SUCC; +} + +/** + * @brief 获取SIM卡状态并更新runtime状态 + */ +static errcode_t n706a_api_update_sim_status(void) +{ + n706a_at_response_t response; + + // 通过AT+CCID命令检查SIM卡状态 + n706a_at_error_t ret = n706a_at_send_command("AT+CCID", "OK", 1000, &response); + if (ret != N706A_AT_OK || !response.success) { + // 如果CCID命令失败,说明SIM卡可能未插入或有问题 + cat1_api_runtime_set_cat1_simstat(0); // 0表示SIM卡异常 + PPP_DEBUG_ERROR("SIM card not detected or error"); + return ERRCODE_FAIL; + } + + // 检查响应中是否包含ERROR + if (strstr(response.response, "ERROR") != NULL) { + cat1_api_runtime_set_cat1_simstat(0); // 0表示SIM卡异常 + PPP_DEBUG_ERROR("SIM card error"); + return ERRCODE_FAIL; + } + + // 如果CCID命令成功,说明SIM卡正常 + cat1_api_runtime_set_cat1_simstat(1); // 1表示SIM卡正常 + PPP_DEBUG_CRITICAL("SIM card detected and normal"); + + return ERRCODE_SUCC; +} + +/** + * @brief 更新所有runtime变量值 + * @return ERRCODE_SUCC-成功,其他-失败 + */ +errcode_t n706a_api_update_all_runtime_status(void) +{ + errcode_t ret = ERRCODE_SUCC; + + PPP_DEBUG_INFO("Updating all N706A runtime status..."); + + // 检查模块是否已开机 + if (!cat1_api_runtime_get_cat1_powerstat()) { + PPP_DEBUG_ERROR("Cat1 not powered on, cannot update runtime status"); + return ERRCODE_FAIL; + } + + // 更新IMEI + PPP_DEBUG_CRITICAL("Step 1: Starting IMEI update..."); + if (n706a_api_update_imei() != ERRCODE_SUCC) { + PPP_DEBUG_ERROR("Failed to update IMEI"); + ret = ERRCODE_FAIL; + } else { + PPP_DEBUG_CRITICAL("Step 1: IMEI update completed successfully"); + } + + // 更新信号强度 + PPP_DEBUG_CRITICAL("Step 2: Starting signal strength update..."); + if (n706a_api_update_signal() != ERRCODE_SUCC) { + PPP_DEBUG_ERROR("Failed to update signal"); + ret = ERRCODE_FAIL; + } else { + PPP_DEBUG_CRITICAL("Step 2: Signal strength update completed successfully"); + } + + // 更新运营商信息 + PPP_DEBUG_CRITICAL("Step 3: Starting operator update..."); + if (n706a_api_update_operator() != ERRCODE_SUCC) { + PPP_DEBUG_ERROR("Failed to update operator"); + ret = ERRCODE_FAIL; + } + + // 更新ICCID + if (n706a_api_update_iccid() != ERRCODE_SUCC) { + PPP_DEBUG_ERROR("Failed to update ICCID"); + ret = ERRCODE_FAIL; + } + + // 更新SIM卡状态 + if (n706a_api_update_sim_status() != ERRCODE_SUCC) { + PPP_DEBUG_ERROR("Failed to update SIM status"); + ret = ERRCODE_FAIL; + } + + // 更新网络状态 + if (n706a_api_update_network_status() != ERRCODE_SUCC) { + PPP_DEBUG_ERROR("Failed to update network status"); + ret = ERRCODE_FAIL; + } + + // 更新版本信息 + if (n706a_api_update_version() != ERRCODE_SUCC) { + PPP_DEBUG_ERROR("Failed to update version"); + ret = ERRCODE_FAIL; + } + + PPP_DEBUG_INFO("N706A runtime status update completed"); + return ret; +} + +/** + * @brief 动态设置N706A模块波特率(在所有模式下) + * + * @param baudrate 目标波特率 + * @return ERRCODE_SUCC 成功,其他值失败 + */ +errcode_t n706a_api_set_baudrate_in_all_mode(uint32_t baudrate) +{ + PPP_DEBUG_INFO("=== Starting baudrate change to %u ===", baudrate); + + // 使用N706A的AT命令接口设置波特率 + n706a_at_error_t ret = n706a_at_set_baudrate(baudrate); + + if (ret != N706A_AT_OK) { + PPP_DEBUG_WARN("First attempt to set baudrate failed, retrying..."); + + // 重试一次 + ret = n706a_at_set_baudrate(baudrate); + if (ret != N706A_AT_OK) { + PPP_DEBUG_ERROR("Failed to set baudrate %u after retry (error: %d)", baudrate, ret); + return ERRCODE_FAIL; + } else { + PPP_DEBUG_INFO("Baudrate set successfully on retry"); + } + } else { + PPP_DEBUG_INFO("Baudrate set successfully: %u", baudrate); + } + + PPP_DEBUG_INFO("=== Baudrate change completed successfully ==="); + return ERRCODE_SUCC; +} + diff --git a/code/sdk/zhihui/n706a_net/src/n706a_tcp_client.c b/code/sdk/zhihui/n706a_net/src/n706a_tcp_client.c index 2d9639d0e..219bf67a3 100644 --- a/code/sdk/zhihui/n706a_net/src/n706a_tcp_client.c +++ b/code/sdk/zhihui/n706a_net/src/n706a_tcp_client.c @@ -8,12 +8,22 @@ */ #include "n706a_tcp_client.h" -#include "ppp_at_handler.h" +#include "n706a_at_handler.h" #include "ppp_types.h" +#include "ppp_serial.h" +#include "ppp_debug.h" +#include "lock/osal_mutex.h" #include #include #include +/* ========== 全局socket管理 ========== */ +#define N706A_MAX_SOCKETS 6 // N706A支持0-5共6个socket + +static n706a_tcp_socket_t *g_tcp_sockets[N706A_MAX_SOCKETS] = {NULL}; +static osal_mutex g_socket_table_mutex; +static bool g_socket_table_initialized = false; + /* ========== 私有函数声明 ========== */ #ifdef N706A_SSL_USE_MBEDTLS @@ -41,6 +51,25 @@ n706a_tcp_result_t n706a_tcp_socket_create(n706a_tcp_socket_t *sock, int socket_ sock->is_ssl = false; sock->timeout_ms = 15000; // 默认15秒超时 + // 初始化URC接收缓存(2KB) + n706a_tcp_result_t ret = n706a_tcp_init_urc_buffer(sock, 2048); + if (ret != N706A_TCP_SUCCESS) { + PPP_DEBUG_ERROR("Failed to init URC buffer for socket %d", socket_id); + return ret; + } + + // 注册到全局socket表 + if (!g_socket_table_initialized) { + osal_mutex_init(&g_socket_table_mutex); + g_socket_table_initialized = true; + } + + osal_mutex_lock(&g_socket_table_mutex); + g_tcp_sockets[socket_id] = sock; + osal_mutex_unlock(&g_socket_table_mutex); + + PPP_DEBUG_INFO("Socket %d created with URC buffer", socket_id); + #ifdef N706A_SSL_USE_AT_COMMAND sock->ssl_version = 3; // 默认TLS 1.2 sock->auth_mode = 1; // 默认服务器认证 @@ -81,17 +110,17 @@ n706a_tcp_result_t n706a_tcp_config_ssl(n706a_tcp_socket_t *sock, /* 设置SSL版本 */ snprintf(at_cmd, sizeof(at_cmd), "AT+SSLTCPCFG=\"sslversion\",%d", ssl_version); - ppp_at_response_t response; - ppp_at_send_command(at_cmd, NULL, 5000, &response); + n706a_at_response_t response; + n706a_at_send_command(at_cmd, NULL, 5000, &response); /* 设置认证模式 */ snprintf(at_cmd, sizeof(at_cmd), "AT+SSLTCPCFG=\"authmode\",%d", auth_mode); - ppp_at_send_command(at_cmd, NULL, 5000, &response); + n706a_at_send_command(at_cmd, NULL, 5000, &response); /* 设置CA证书 */ if (ca_cert && strlen(ca_cert) > 0) { snprintf(at_cmd, sizeof(at_cmd), "AT+SSLTCPCFG=\"cacert\",\"%s\"", ca_cert); - ppp_at_send_command(at_cmd, NULL, 5000, &response); + n706a_at_send_command(at_cmd, NULL, 5000, &response); } } #endif @@ -179,8 +208,8 @@ n706a_tcp_result_t n706a_tcp_connect(n706a_tcp_socket_t *sock, sock->timeout_ms = timeout_ms; char at_cmd[256]; - ppp_at_response_t response; - ppp_error_t ret; + n706a_at_response_t response; + n706a_at_error_t ret; sock->state = N706A_TCP_STATE_CONNECTING; @@ -194,13 +223,25 @@ n706a_tcp_result_t n706a_tcp_connect(n706a_tcp_socket_t *sock, port, sock->auth_mode); - ret = ppp_at_send_command(at_cmd, NULL, timeout_ms, &response); + ret = n706a_at_send_command(at_cmd, NULL, timeout_ms, &response); + + PPP_DEBUG_CRITICAL("[N706A_TCP] AT+SSLTCPSETUP response: ret=%d, len=%u", ret, response.length); + if (response.length > 0) { + PPP_DEBUG_CRITICAL("[N706A_TCP] Response: %s", response.response); + } - if (ret == PPP_ERR_OK && response.length > 0 && - strstr(response.response, "+SSLTCPSETUP:") && - strstr(response.response, ",OK")) { + /* 检查响应是否包含OK且不包含ERROR */ + if (ret == N706A_AT_OK && response.length > 0 && + strstr(response.response, "OK") && + !strstr(response.response, "ERROR")) { + PPP_DEBUG_CRITICAL("[N706A_TCP] SSL TCP connection established successfully!"); sock->state = N706A_TCP_STATE_CONNECTED; return N706A_TCP_SUCCESS; + } else { + PPP_DEBUG_ERROR("[N706A_TCP] SSL TCP connection failed! Response: %s", + response.length > 0 ? response.response : "(empty)"); + sock->state = N706A_TCP_STATE_ERROR; + return N706A_TCP_ECONNREF; } } else { /* 普通TCP */ @@ -210,13 +251,25 @@ n706a_tcp_result_t n706a_tcp_connect(n706a_tcp_socket_t *sock, host, port); - ret = ppp_at_send_command(at_cmd, NULL, timeout_ms, &response); + ret = n706a_at_send_command(at_cmd, NULL, timeout_ms, &response); - if (ret == PPP_ERR_OK && response.length > 0 && - strstr(response.response, "+TCPSETUP:") && - strstr(response.response, ",OK")) { + PPP_DEBUG_CRITICAL("[N706A_TCP] AT+TCPSETUP response: ret=%d, len=%u", ret, response.length); + if (response.length > 0) { + PPP_DEBUG_CRITICAL("[N706A_TCP] Response: %s", response.response); + } + + /* 检查响应是否包含OK且不包含ERROR */ + if (ret == N706A_AT_OK && response.length > 0 && + strstr(response.response, "OK") && + !strstr(response.response, "ERROR")) { + PPP_DEBUG_CRITICAL("[N706A_TCP] TCP connection established successfully!"); sock->state = N706A_TCP_STATE_CONNECTED; return N706A_TCP_SUCCESS; + } else { + PPP_DEBUG_ERROR("[N706A_TCP] TCP connection failed! Response: %s", + response.length > 0 ? response.response : "(empty)"); + sock->state = N706A_TCP_STATE_ERROR; + return N706A_TCP_ECONNREF; } } #endif @@ -231,48 +284,88 @@ n706a_tcp_result_t n706a_tcp_connect(n706a_tcp_socket_t *sock, host, port); - ret = ppp_at_send_command(at_cmd, NULL, timeout_ms, &response); + ret = n706a_at_send_command(at_cmd, NULL, timeout_ms, &response); - if (ret != PPP_ERR_OK || response.length == 0 || - !strstr(response.response, "+TCPSETUP:") || - !strstr(response.response, ",OK")) { + PPP_DEBUG_CRITICAL("[N706A_TCP] AT+TCPSETUP (mbedtls mode) response: ret=%d, len=%u", ret, response.length); + if (response.length > 0) { + PPP_DEBUG_CRITICAL("[N706A_TCP] Response: %s", response.response); + } + + /* N706A的AT+TCPSETUP成功响应格式: + * AT+TCPSETUP=0,host,port + * OK + * + * 只需检查是否包含"OK"且不包含"ERROR" + */ + if (ret != N706A_AT_OK || response.length == 0 || + !strstr(response.response, "OK") || + strstr(response.response, "ERROR")) { + PPP_DEBUG_ERROR("[N706A_TCP] TCP connection failed! Response: %s", + response.length > 0 ? response.response : "(empty)"); sock->state = N706A_TCP_STATE_ERROR; return N706A_TCP_ECONNREF; } + PPP_DEBUG_CRITICAL("[N706A_TCP] TCP connection established successfully!"); + + /* TCP连接已建立,标记为已连接(如果不使用SSL) */ + if (!use_ssl) { + sock->state = N706A_TCP_STATE_CONNECTED; + return N706A_TCP_SUCCESS; + } + /* 2. 如果使用SSL,进行SSL握手 */ if (use_ssl) { + PPP_DEBUG_CRITICAL("[N706A_TCP] Starting SSL handshake (mbedtls)..."); + n706a_tcp_result_t ssl_ret = tcp_mbedtls_init_ssl(sock); if (ssl_ret != N706A_TCP_SUCCESS) { + PPP_DEBUG_ERROR("[N706A_TCP] Failed to initialize SSL context!"); n706a_tcp_close(sock); return ssl_ret; } + PPP_DEBUG_CRITICAL("[N706A_TCP] SSL context initialized, setting hostname: %s", host); + /* 设置主机名(用于SNI) */ mbedtls_ssl_set_hostname(&sock->ssl, host); /* 执行SSL握手 */ + PPP_DEBUG_CRITICAL("[N706A_TCP] Performing SSL handshake..."); int handshake_ret; + int handshake_attempts = 0; while ((handshake_ret = mbedtls_ssl_handshake(&sock->ssl)) != 0) { + handshake_attempts++; if (handshake_ret != MBEDTLS_ERR_SSL_WANT_READ && handshake_ret != MBEDTLS_ERR_SSL_WANT_WRITE) { /* 握手失败 */ + PPP_DEBUG_ERROR("[N706A_TCP] SSL handshake failed after %d attempts! Error: -0x%04x", + handshake_attempts, -handshake_ret); tcp_mbedtls_cleanup_ssl(sock); n706a_tcp_close(sock); sock->state = N706A_TCP_STATE_ERROR; return N706A_TCP_ERROR; } + + if (handshake_attempts % 10 == 0) { + PPP_DEBUG_DEBUG("[N706A_TCP] SSL handshake in progress... (attempt %d)", handshake_attempts); + } } + PPP_DEBUG_CRITICAL("[N706A_TCP] SSL handshake completed successfully after %d attempts", handshake_attempts); + /* 验证证书 */ uint32_t flags = mbedtls_ssl_get_verify_result(&sock->ssl); if (flags != 0 && sock->auth_mode != MBEDTLS_SSL_VERIFY_NONE) { /* 证书验证失败 */ + PPP_DEBUG_ERROR("[N706A_TCP] Certificate verification failed! Flags: 0x%08x", flags); tcp_mbedtls_cleanup_ssl(sock); n706a_tcp_close(sock); sock->state = N706A_TCP_STATE_ERROR; return N706A_TCP_ERROR; } + + PPP_DEBUG_CRITICAL("[N706A_TCP] Certificate verified successfully"); } #endif @@ -311,7 +404,7 @@ n706a_tcp_result_t n706a_tcp_send(n706a_tcp_socket_t *sock, /* AT指令模式或普通TCP */ char at_cmd[64]; - ppp_at_response_t response; + n706a_at_response_t response; #ifdef N706A_SSL_USE_AT_COMMAND if (sock->is_ssl) { @@ -325,14 +418,14 @@ n706a_tcp_result_t n706a_tcp_send(n706a_tcp_socket_t *sock, } /* 发送命令,等待'>'提示符 */ - ppp_error_t ret = ppp_at_send_command(at_cmd, NULL, 5000, &response); - if (ret != PPP_ERR_OK) { + n706a_at_error_t ret = n706a_at_send_command(at_cmd, NULL, 5000, &response); + if (ret != N706A_AT_OK) { return N706A_TCP_ERROR; } /* 发送实际数据 */ ret = ppp_at_send_raw_data(data, len, sock->timeout_ms); - if (ret != PPP_ERR_OK) { + if (ret != N706A_AT_OK) { return N706A_TCP_ERROR; } @@ -357,6 +450,29 @@ n706a_tcp_result_t n706a_tcp_recv(n706a_tcp_socket_t *sock, return N706A_TCP_ENOTCONN; } + /* 优先从URC缓存读取数据(如果有) */ + if (sock->urc_recv_buffer && sock->urc_recv_mutex && sock->urc_recv_length > 0) { + osal_mutex *mutex = (osal_mutex *)sock->urc_recv_mutex; + osal_mutex_lock(mutex); + + size_t copy_len = (sock->urc_recv_length < len) ? sock->urc_recv_length : len; + memcpy(buffer, sock->urc_recv_buffer, copy_len); + + // 移动剩余数据到缓冲区开头 + if (copy_len < sock->urc_recv_length) { + memmove(sock->urc_recv_buffer, sock->urc_recv_buffer + copy_len, + sock->urc_recv_length - copy_len); + } + sock->urc_recv_length -= copy_len; + + osal_mutex_unlock(mutex); + + *received = copy_len; + PPP_DEBUG_DEBUG("Read from URC buffer: socket=%d, len=%u, remaining=%u", + sock->socket_id, (unsigned int)copy_len, (unsigned int)sock->urc_recv_length); + return N706A_TCP_SUCCESS; + } + #ifdef N706A_SSL_USE_MBEDTLS /* mbedtls模式:通过SSL接收 */ if (sock->is_ssl && sock->ssl_initialized) { @@ -377,7 +493,7 @@ n706a_tcp_result_t n706a_tcp_recv(n706a_tcp_socket_t *sock, /* AT指令模式或普通TCP */ char at_cmd[64]; - ppp_at_response_t response; + n706a_at_response_t response; #ifdef N706A_SSL_USE_AT_COMMAND if (sock->is_ssl) { @@ -390,8 +506,8 @@ n706a_tcp_result_t n706a_tcp_recv(n706a_tcp_socket_t *sock, sock->socket_id, len); } - ppp_error_t ret = ppp_at_send_command(at_cmd, NULL, timeout_ms, &response); - if (ret != PPP_ERR_OK) { + n706a_at_error_t ret = n706a_at_send_command(at_cmd, NULL, timeout_ms, &response); + if (ret != N706A_AT_OK) { return N706A_TCP_ETIMEDOUT; } @@ -440,7 +556,7 @@ n706a_tcp_result_t n706a_tcp_close(n706a_tcp_socket_t *sock) /* 发送关闭命令 */ char at_cmd[32]; - ppp_at_response_t response; + n706a_at_response_t response; #ifdef N706A_SSL_USE_AT_COMMAND if (sock->is_ssl) { @@ -451,11 +567,23 @@ n706a_tcp_result_t n706a_tcp_close(n706a_tcp_socket_t *sock) snprintf(at_cmd, sizeof(at_cmd), "AT+TCPCLOSE=%d", sock->socket_id); } - ppp_at_send_command(at_cmd, NULL, 5000, &response); + n706a_at_send_command(at_cmd, NULL, 5000, &response); sock->state = N706A_TCP_STATE_CLOSED; sock->is_ssl = false; + // 从全局socket表中移除 + if (g_socket_table_initialized) { + osal_mutex_lock(&g_socket_table_mutex); + g_tcp_sockets[sock->socket_id] = NULL; + osal_mutex_unlock(&g_socket_table_mutex); + } + + // 释放URC接收缓存 + n706a_tcp_free_urc_buffer(sock); + + PPP_DEBUG_INFO("Socket %d closed and URC buffer freed", sock->socket_id); + return N706A_TCP_SUCCESS; } @@ -600,15 +728,15 @@ static int tcp_mbedtls_send_callback(void *ctx, const unsigned char *buf, size_t snprintf(at_cmd, sizeof(at_cmd), "AT+TCPSEND=%d,%zu", sock->socket_id, len); - ppp_at_response_t response; - ppp_error_t ret = ppp_at_send_command(at_cmd, NULL, 5000, &response); - if (ret != PPP_ERR_OK) { + n706a_at_response_t response; + n706a_at_error_t ret = n706a_at_send_command(at_cmd, NULL, 5000, &response); + if (ret != N706A_AT_OK) { return MBEDTLS_ERR_NET_SEND_FAILED; } /* 发送数据 */ ret = ppp_at_send_raw_data(buf, len, sock->timeout_ms); - if (ret != PPP_ERR_OK) { + if (ret != N706A_AT_OK) { return MBEDTLS_ERR_NET_SEND_FAILED; } @@ -631,9 +759,9 @@ static int tcp_mbedtls_recv_callback(void *ctx, unsigned char *buf, size_t len) snprintf(at_cmd, sizeof(at_cmd), "AT+TCPREAD=%d,%zu", sock->socket_id, len); - ppp_at_response_t response; - ppp_error_t ret = ppp_at_send_command(at_cmd, NULL, sock->timeout_ms, &response); - if (ret != PPP_ERR_OK) { + n706a_at_response_t response; + n706a_at_error_t ret = n706a_at_send_command(at_cmd, NULL, sock->timeout_ms, &response); + if (ret != N706A_AT_OK) { return MBEDTLS_ERR_NET_RECV_FAILED; } @@ -658,3 +786,118 @@ static int tcp_mbedtls_recv_callback(void *ctx, unsigned char *buf, size_t len) #endif /* N706A_SSL_USE_MBEDTLS */ +/* ========== URC接收支持 ========== */ + +/** + * @brief 初始化TCP socket的URC接收缓存 + */ +n706a_tcp_result_t n706a_tcp_init_urc_buffer(n706a_tcp_socket_t *sock, size_t buffer_size) +{ + if (!sock || buffer_size == 0) { + return N706A_TCP_EINVAL; + } + + // 分配缓冲区 + sock->urc_recv_buffer = (uint8_t *)malloc(buffer_size); + if (!sock->urc_recv_buffer) { + PPP_DEBUG_ERROR("Failed to allocate URC buffer (%u bytes)", (unsigned int)buffer_size); + return N706A_TCP_ENOBUFS; + } + + sock->urc_recv_capacity = buffer_size; + sock->urc_recv_length = 0; + + // 初始化互斥锁 + osal_mutex *mutex = (osal_mutex *)malloc(sizeof(osal_mutex)); + if (!mutex) { + free(sock->urc_recv_buffer); + sock->urc_recv_buffer = NULL; + return N706A_TCP_ENOBUFS; + } + + osal_mutex_init(mutex); + sock->urc_recv_mutex = mutex; + + PPP_DEBUG_DEBUG("URC buffer initialized: socket=%d, size=%u", + sock->socket_id, (unsigned int)buffer_size); + + return N706A_TCP_SUCCESS; +} + +/** + * @brief 释放TCP socket的URC接收缓存 + */ +void n706a_tcp_free_urc_buffer(n706a_tcp_socket_t *sock) +{ + if (!sock) { + return; + } + + if (sock->urc_recv_mutex) { + osal_mutex *mutex = (osal_mutex *)sock->urc_recv_mutex; + osal_mutex_destroy(mutex); + free(mutex); + sock->urc_recv_mutex = NULL; + } + + if (sock->urc_recv_buffer) { + free(sock->urc_recv_buffer); + sock->urc_recv_buffer = NULL; + } + + sock->urc_recv_capacity = 0; + sock->urc_recv_length = 0; + + PPP_DEBUG_DEBUG("URC buffer freed: socket=%d", sock->socket_id); +} + +/** + * @brief 全局TCP URC数据接收回调(由n706a_at_handler调用) + */ +void n706a_tcp_on_urc_received(int socket_id, const uint8_t *data, size_t length) +{ + if (socket_id < 0 || socket_id >= N706A_MAX_SOCKETS || !data || length == 0) { + PPP_DEBUG_ERROR("Invalid URC parameters: socket=%d, len=%u", socket_id, (unsigned int)length); + return; + } + + // 获取socket + osal_mutex_lock(&g_socket_table_mutex); + n706a_tcp_socket_t *sock = g_tcp_sockets[socket_id]; + osal_mutex_unlock(&g_socket_table_mutex); + + if (!sock) { + PPP_DEBUG_WARN("URC for unregistered socket %d, dropping %u bytes", socket_id, (unsigned int)length); + return; + } + + if (!sock->urc_recv_buffer || !sock->urc_recv_mutex) { + PPP_DEBUG_ERROR("URC buffer not initialized for socket %d", socket_id); + return; + } + + // 写入缓冲区(线程安全) + osal_mutex *mutex = (osal_mutex *)sock->urc_recv_mutex; + osal_mutex_lock(mutex); + + size_t available_space = sock->urc_recv_capacity - sock->urc_recv_length; + size_t copy_len = (length <= available_space) ? length : available_space; + + if (copy_len < length) { + PPP_DEBUG_WARN("URC buffer overflow: socket=%d, need=%u, available=%u", + socket_id, (unsigned int)length, (unsigned int)available_space); + } + + if (copy_len > 0) { + memcpy(sock->urc_recv_buffer + sock->urc_recv_length, data, copy_len); + sock->urc_recv_length += copy_len; + + PPP_DEBUG_INFO("URC data buffered: socket=%d, received=%u, total=%u/%u", + socket_id, (unsigned int)copy_len, + (unsigned int)sock->urc_recv_length, + (unsigned int)sock->urc_recv_capacity); + } + + osal_mutex_unlock(mutex); +} + diff --git a/code/sdk/zhihui/n706a_net/src/n706a_websocket.c b/code/sdk/zhihui/n706a_net/src/n706a_websocket.c index 70b86a2a6..02e9eb9f3 100644 --- a/code/sdk/zhihui/n706a_net/src/n706a_websocket.c +++ b/code/sdk/zhihui/n706a_net/src/n706a_websocket.c @@ -429,7 +429,12 @@ int n706a_websocket_destructor(int client_id) */ static int ws_perform_handshake(n706a_websocket_client_t *client) { - char handshake_req[1024]; + /* 动态分配握手请求缓冲区 */ + char *handshake_req = (char *)malloc(1024); + if (!handshake_req) { + return -1; + } + char sec_key[25]; /* 生成Sec-WebSocket-Key */ @@ -437,7 +442,7 @@ static int ws_perform_handshake(n706a_websocket_client_t *client) strncpy(client->sec_key, sec_key, sizeof(client->sec_key) - 1); /* 构造握手请求 */ - int len = snprintf(handshake_req, sizeof(handshake_req), + int len = snprintf(handshake_req, 1024, "GET %s HTTP/1.1\r\n" "Host: %s\r\n" "Upgrade: websocket\r\n" @@ -457,21 +462,27 @@ static int ws_perform_handshake(n706a_websocket_client_t *client) len, &sent); + free(handshake_req); + if (ret != N706A_TCP_SUCCESS) { return -1; } - /* 接收握手响应 */ - uint8_t response[2048]; - size_t received; + /* 动态分配响应缓冲区 */ + uint8_t *response = (uint8_t *)malloc(2048); + if (!response) { + return -1; + } + size_t received; ret = n706a_tcp_recv(&client->tcp_sock, response, - sizeof(response) - 1, + 2048 - 1, &received, 10000); if (ret != N706A_TCP_SUCCESS || received == 0) { + free(response); return -1; } @@ -481,6 +492,7 @@ static int ws_perform_handshake(n706a_websocket_client_t *client) if (!strstr((char *)response, "HTTP/1.1 101") || !strstr((char *)response, "Upgrade: websocket") || !strstr((char *)response, "Connection: Upgrade")) { + free(response); return -1; } @@ -494,10 +506,12 @@ static int ws_perform_handshake(n706a_websocket_client_t *client) n706a_websocket_calculate_accept(sec_key, expected_accept); if (strncmp(accept_start, expected_accept, strlen(expected_accept)) != 0) { + free(response); return -1; // Accept值不匹配 } } + free(response); return 0; } diff --git a/code/sdk/zhihui/n706a_net/src/ppp_cat1_api.c b/code/sdk/zhihui/n706a_net/src/ppp_cat1_api.c new file mode 100644 index 000000000..28b4d8fe1 --- /dev/null +++ b/code/sdk/zhihui/n706a_net/src/ppp_cat1_api.c @@ -0,0 +1,512 @@ +/* + * @brief PPP模块的cat1 API兼容实现 + * @description 实现与cat1Service相同的接口,让UI和其他服务无需修改 + * Copyright (c) CompanyNameMagicTag 2024-2024. All rights reserved. + */ + +#include "ppp_power_ctl.h" +#include "ppp_debug.h" +#include "ppp_types.h" +#include "errcode.h" +#include "securec.h" +#include "platform_core.h" +#include +#include "pm_definition.h" +#include "pm.h" + +// 兼容cat1Service的类型定义 +typedef enum _4g_message { + MESSAGE_4G_POWERON,//无参数 (BOOTUP) + MESSAGE_4G_POWEROFF,//无参数 + MESSAGE_4G_NETWORK_CONNECTED,//无参数 + MESSAGE_4G_NETWORK_DISCONNECTED,//无参数 + MESSAGE_4G_NETWORK_STAT_CHANGED,//含signal,sim,运营商, + MESSAGE_4G_CALL_COMMING,//带电话号 + MESSAGE_4G_CALL_CONNECTED,//带电话号 + MESSAGE_4G_CALL_DISCONNECTED,//带电话号 + MESSAGE_4G_SMS_RECEIVED, //返回值为字符串 + MESSAGE_4G_AT_RECEIVED, //返回值为字符串 + MESSAGE_4G_TIME_SYNCED,//时间同步完成 + MESSAGE_4G_VERSION_SYNCED,//版本同步完成 + MESSAGE_4G_MAX, +} MESSAGE_4G_T; + +typedef enum _4g_cmd { + CMD_4G_POWERON,//无参数 + CMD_4G_POWEROFF,//无参数 + CMD_4G_GET_SIGNAL,//无参数 + CMD_4G_GET_VERSION,//无参数 + CMD_4G_GET_TIME,//无参数 + CMD_4G_MAKE_CALL,//需要输入参数 电话号 + CMD_4G_ANSWER_CALL,//无参数 + CMD_4G_HUNGUP_CALL,//无参数 + CMD_4G_SEND_DTMF, //需要输入参数 DTMF NUM(单个数字) , 包括 0 ~ 9, * , # + CMD_4G_AT, //需要AT指令,要求结尾带\r\n + CMD_4G_MAX, +} CMD_4G_T; + +typedef struct { + uint8_t bat_pin; //控制Cat1 battery 的gpio,没有控制,设置为 PIN_NONE 即可 + uint8_t bat_pin_en_value; + uint8_t pwr_pin; //控制cat1开机键的gpio + uint8_t pwr_pin_en_value; + uint8_t reset_pin; //控制cat1重置键的gpio + uint8_t reset_pin_en_value; + uint8_t ring_pin; //cat1唤醒蓝牙用的gpio + uint8_t ring_pin_en_value; + uint8_t wakemodem_pin; //蓝牙唤醒cat1用的gpio + uint8_t wakemodem_pin_en_value; +} ctl_pin_4g_t; + +// 消息回调函数类型 +typedef errcode_t (*f_cat1_msg_callback)(MESSAGE_4G_T msg, void *msg_buf, uint16_t msglen); + +// 时间控制函数类型 +typedef uint16_t (*f_get_timezone)(void); +typedef void (*f_set_timezone)(uint16_t timezone_quarters); +typedef uint32_t (*f_sync_time_utc)(uint32_t timestamp, uint32_t timezone_quarters); + +typedef struct { + f_get_timezone get_timezone; + f_set_timezone set_timezone; + f_sync_time_utc sync_time_utc; +} time_ctl_funcs_t; + +// PPP模块的运行时状态结构(与cat1Service保持一致) +typedef struct { + uint8_t cat1_powerOn; + uint8_t volte; + uint8_t network_on; + uint8_t cat1_network_type; + int sim; + int signal; + int rsrp; + int rsrq; + char opera[64]; + char imei[32]; + char pyVer[32]; + char iccid[32]; + char modemVer[64]; +} ppp_cat1_runtime_status_t; + +typedef struct { + int rsrp; + int rsrq; +} ppp_cat1_signal_info_t; + +// PPP模块的运行时状态 +static ppp_cat1_runtime_status_t g_ppp_runtime = {0}; + +// PPP模块的控制引脚配置 +static ppp_ctl_pin_4g_t g_ppp_ctl_pins = {0}; + +// PPP模块的初始化状态 +static bool g_ppp_cat1_initialized = false; + +/********************** 电源控制接口 **********************/ + +/** + * @brief cat1 开关机设置, 输入 true 开机, false 关机 + */ +void cat1_api_enable_cat1(bool isopen) +{ + PPP_DEBUG_PRINT("PPP: cat1_api_enable_cat1(%s)\n", isopen ? "true" : "false"); + + if (isopen) { + if (g_ppp_runtime.cat1_powerOn) { + PPP_DEBUG_PRINT("PPP: Cat1 already powered on\n"); + return; + } + + // PPP模块开启4G + ppp_power_on_cat1(); + g_ppp_runtime.cat1_powerOn = 1; + + // 可以在这里添加PPP特定的初始化逻辑 + // 例如:启动PPP协议栈等 + + } else { + if (!g_ppp_runtime.cat1_powerOn) { + PPP_DEBUG_PRINT("PPP: Cat1 already powered off\n"); + return; + } + + // PPP模块关闭4G + ppp_power_off_cat1(); + g_ppp_runtime.cat1_powerOn = 0; + + // 清理网络状态 + g_ppp_runtime.network_on = 0; + g_ppp_runtime.signal = 99; // 无信号 + g_ppp_runtime.sim = 0; + g_ppp_runtime.cat1_network_type = 0; + } +} + +/** + * @brief cat1 唤醒设置, 输入 true 唤醒cat1 modem, false cat1 modem自动休眠 + */ +void cat1_api_wakeup_cat1(uint8_t wakeup) +{ + ppp_api_wakeup_cat1(wakeup); +} + +/** + * @brief 获取cat1是否使能(开机)状态 + */ +bool cat1_api_is_cat1_enabled(void) +{ + return ppp_api_is_cat1_enabled(); +} + +/** + * @brief 获取Cat1 ICCID号 + */ +char* cat1_api_runtime_get_cat1_iccid(void) +{ + return g_ppp_runtime.iccid; +} + +/** + * @brief 设置Cat1 ICCID号 + */ +void cat1_api_runtime_set_cat1_iccid(char* iccid) +{ + if (iccid != NULL) { + (void)strncpy_s(g_ppp_runtime.iccid, sizeof(g_ppp_runtime.iccid), + iccid, sizeof(g_ppp_runtime.iccid) - 1); + g_ppp_runtime.iccid[sizeof(g_ppp_runtime.iccid) - 1] = '\0'; + } +} + +/** + * @brief return cmd uart rx enable flag + */ +bool cat1_api_custom_is_cmd_rx_enabled(void) +{ + // PPP模块默认启用接收 + return true; +} + +void cat1_api_remove_cat1_vote(void) +{ + uint8_t cnt = pm_veto_get_id_cnt(PM_ID_CAT1); + if (cnt == 1) { + uapi_pm_remove_sleep_veto(PM_ID_CAT1); + } else { + // PPP_DEBUG_PRINT("CAT1 SERVICE MCU has delete pm_id_cat1 vote\r\n"); + } + PPP_DEBUG_PRINT("\r\nCAT1 SERVICE MCU SLEEP SUCCESS\r\n"); +} + +void cat1_api_add_cat1_vote(void) +{ + uint8_t cnt = pm_veto_get_id_cnt(PM_ID_CAT1); + if (cnt == 0) { + uapi_pm_add_sleep_veto(PM_ID_CAT1); + } else { + // PPP_DEBUG_PRINT("CAT1 SERVICE MCU has got pm_id_cat1 vote\r\n"); + } + PPP_DEBUG_PRINT("\r\nCAT1 SERVICE MCU WAKEUP SUCCESS\r\n"); +} + +errcode_t cat1_api_disable_cat1_cmd(uint8_t disable_cmd) +{ + return ERRCODE_SUCC; +} + +/********************** 运行时状态获取接口 **********************/ + +/** + * @brief 获取cat1电源开启状态 + */ +uint8_t cat1_api_runtime_get_cat1_powerstat(void) +{ + return g_ppp_runtime.cat1_powerOn; +} + +/** + * @brief 获取cat1网络状态 + */ +uint8_t cat1_api_runtime_get_cat1_networkstat(void) +{ + return g_ppp_runtime.network_on; +} + +/** + * @brief 获取cat1信号强度 + */ +int cat1_api_runtime_get_cat1_signal(void) +{ + return g_ppp_runtime.signal; +} + +/** + * @brief 获取sim卡状态 + */ +int cat1_api_runtime_get_cat1_simstat(void) +{ + return g_ppp_runtime.sim; +} + +/** + * @brief 获取网络类型 + */ +uint8_t cat1_api_runtime_get_cat1_networktype(void) +{ + return g_ppp_runtime.cat1_network_type; +} + +/** + * @brief 获取Cat1运营商信息 + */ +char* cat1_api_runtime_get_cat1_operator(void) +{ + return g_ppp_runtime.opera; +} + +/** + * @brief 获取Cat1 IMEI号 + */ +char* cat1_api_runtime_get_cat1_imei(void) +{ + return g_ppp_runtime.imei; +} + +/** + * @brief 获取Cat1主应用版本号 + */ +char* cat1_api_runtime_get_cat1_pyver(void) +{ + return g_ppp_runtime.pyVer; +} + +/** + * @brief 获取modem版本 + */ +char* cat1_api_runtime_get_cat1_modemver(void) +{ + return g_ppp_runtime.modemVer; +} + +/********************** 运行时状态设置接口 **********************/ + +/** + * @brief 设置cat1电源状态 + */ +void cat1_api_runtime_set_cat1_powerstat(uint8_t cat1_powerOn) +{ + g_ppp_runtime.cat1_powerOn = cat1_powerOn; +} + +/** + * @brief 设置cat1网络状态 + */ +void cat1_api_runtime_set_cat1_networkstat(uint8_t network_on) +{ + g_ppp_runtime.network_on = network_on; +} + +/** + * @brief 设置cat1信号强度 + */ +void cat1_api_runtime_set_cat1_signal(int signal) +{ + g_ppp_runtime.signal = signal; +} + +/** + * @brief 设置sim卡状态 + */ +void cat1_api_runtime_set_cat1_simstat(int simstat) +{ + g_ppp_runtime.sim = simstat; +} + +/** + * @brief 设置网络类型 + */ +void cat1_api_runtime_set_cat1_networktype(uint8_t networktype) +{ + g_ppp_runtime.cat1_network_type = networktype; +} + +/** + * @brief 设置Cat1运营商信息 + */ +void cat1_api_runtime_set_cat1_operator(char* cat1_operator) +{ + if (cat1_operator != NULL) { + (void)strncpy_s(g_ppp_runtime.opera, sizeof(g_ppp_runtime.opera), + cat1_operator, sizeof(g_ppp_runtime.opera) - 1); + g_ppp_runtime.opera[sizeof(g_ppp_runtime.opera) - 1] = '\0'; + } +} + +/** + * @brief 设置Cat1 IMEI号 + */ +void cat1_api_runtime_set_cat1_imei(char* imei) +{ + if (imei != NULL) { + (void)strncpy_s(g_ppp_runtime.imei, sizeof(g_ppp_runtime.imei), + imei, sizeof(g_ppp_runtime.imei) - 1); + g_ppp_runtime.imei[sizeof(g_ppp_runtime.imei) - 1] = '\0'; + } +} + +/** + * @brief 设置Cat1主应用版本号 + */ +void cat1_api_runtime_set_cat1_pyver(char* pyver) +{ + if (pyver != NULL) { + (void)strncpy_s(g_ppp_runtime.pyVer, sizeof(g_ppp_runtime.pyVer), + pyver, sizeof(g_ppp_runtime.pyVer) - 1); + g_ppp_runtime.pyVer[sizeof(g_ppp_runtime.pyVer) - 1] = '\0'; + } +} + +/** + * @brief 设置modem版本 + */ +void cat1_api_runtime_set_cat1_modemver(char* modemver) +{ + if (modemver != NULL) { + (void)strncpy_s(g_ppp_runtime.modemVer, sizeof(g_ppp_runtime.modemVer), + modemver, sizeof(g_ppp_runtime.modemVer) - 1); + g_ppp_runtime.modemVer[sizeof(g_ppp_runtime.modemVer) - 1] = '\0'; + } +} + +/********************** 控制接口 **********************/ + + +/** + * @brief 保持唤醒Cat1 + */ +void cat1_api_keep_wakeup(uint8_t keep_wakeup) +{ + ppp_api_keep_wakeup(keep_wakeup); +} + +/** + * @brief 注册控制4G的gpio值 + */ +errcode_t cat1_register_ctl_pin(ctl_pin_4g_t ctl_pin) +{ + // 转换为PPP模块的引脚配置结构 + ppp_ctl_pin_4g_t ppp_pin_config = { + .bat_pin = ctl_pin.bat_pin, + .bat_pin_en_value = ctl_pin.bat_pin_en_value, + .pwr_pin = ctl_pin.pwr_pin, + .pwr_pin_en_value = ctl_pin.pwr_pin_en_value, + .reset_pin = ctl_pin.reset_pin, + .reset_pin_en_value = ctl_pin.reset_pin_en_value, + .ring_pin = ctl_pin.ring_pin, + .ring_pin_en_value = ctl_pin.ring_pin_en_value, + .wakemodem_pin = ctl_pin.wakemodem_pin, + .wakemodem_pin_en_value = ctl_pin.wakemodem_pin_en_value + }; + + return ppp_register_ctl_pin(&ppp_pin_config); +} + +/** + * @brief cat1唤醒蓝牙中断处理函数 + */ +void cat1_ring_isr_callback(pin_t pin, uintptr_t param) +{ + ppp_ring_isr_callback(pin, param); +} + +/********************** 协议栈休眠/唤醒接口 (PPP专用) **********************/ + +/** + * @brief 挂起PPP协议栈 (PPP模式专用实现) + * + * 功能: + * 1. 挂起lwIP tcpip线程 + * 2. 挂起PPP接收/处理线程 + * 3. 停止PPP定时器 + */ +void cat1_api_suspend_protocol(void) +{ + PPP_DEBUG_INFO("=== PPP: cat1_api_suspend_protocol (PPP mode) ===\n"); + + // 1. 挂起lwIP tcpip线程 + #ifdef SUPPORT_LWIP + tcpip_thread_suspend(); + PPP_DEBUG_INFO("PPP: lwIP tcpip thread suspended\n"); + #endif + + // 2. 挂起PPP协议栈 (PPP专用) + extern void ppp_suspend(void); + ppp_suspend(); + PPP_DEBUG_INFO("PPP: PPP protocol stack suspended\n"); + + PPP_DEBUG_INFO("=== PPP: Protocol stack suspended successfully ===\n"); +} + +/** + * @brief 恢复PPP协议栈 (PPP模式专用实现) + * + * 功能: + * 1. 恢复lwIP tcpip线程 + * 2. 恢复PPP接收/处理线程 + * 3. 重启PPP定时器 + */ +void cat1_api_resume_protocol(void) +{ + PPP_DEBUG_INFO("=== PPP: cat1_api_resume_protocol (PPP mode) ===\n"); + + // 1. 恢复lwIP tcpip线程 + #ifdef SUPPORT_LWIP + tcpip_thread_resume(); + PPP_DEBUG_INFO("PPP: lwIP tcpip thread resumed\n"); + #endif + + // 2. 恢复PPP协议栈 (PPP专用) + extern void ppp_resume(void); + ppp_resume(); + PPP_DEBUG_INFO("PPP: PPP protocol stack resumed\n"); + + PPP_DEBUG_INFO("=== PPP: Protocol stack resumed successfully ===\n"); +} + +/********************** 其他接口(暂时返回默认值或空实现) **********************/ + +/** + * @brief 发送指令给Cat1 + */ +errcode_t cat1_cmd_send(CMD_4G_T cmd, void *cmd_buf, uint16_t cmdlen) +{ + PPP_DEBUG_PRINT("PPP: cat1_cmd_send cmd=%d, len=%d\n", cmd, cmdlen); + // TODO: 实现PPP模块的AT命令发送 + (void)cmd; + (void)cmd_buf; + (void)cmdlen; + return ERRCODE_SUCC; +} + +/** + * @brief 注册消息回调函数 + */ +errcode_t cat1_register_msg_callback(f_cat1_msg_callback func) +{ + PPP_DEBUG_PRINT("PPP: cat1_register_msg_callback\n"); + // TODO: 实现PPP模块的消息回调注册 + (void)func; + return ERRCODE_SUCC; +} + +/** + * @brief 注册时间控制函数 + */ +errcode_t cat1_register_time_ctl_functions(time_ctl_funcs_t *funcs) +{ + PPP_DEBUG_PRINT("PPP: cat1_register_time_ctl_functions\n"); + // TODO: 实现PPP模块的时间控制函数注册 + (void)funcs; + return ERRCODE_SUCC; +} \ No newline at end of file diff --git a/code/sdk/zhihui/n706a_net/src/ppp_debug.c b/code/sdk/zhihui/n706a_net/src/ppp_debug.c new file mode 100644 index 000000000..44e098afa --- /dev/null +++ b/code/sdk/zhihui/n706a_net/src/ppp_debug.c @@ -0,0 +1,119 @@ +#include "ppp_debug.h" +#include "ppp_config.h" +#include "ppp_types.h" +#include + +#if PPP_DEBUG_ENABLE + +/** + * @brief 十六进制数据转储 + */ +void ppp_debug_hexdump(const uint8_t *data, uint32_t len) +{ + if (!data || len == 0) { + return; + } + + PRINT("[PPP HEXDUMP] Length: %u bytes\r\n", len); + + for (uint32_t i = 0; i < len; i += 16) { + /* 打印地址 */ + PRINT("%08X: ", i); + + /* 打印十六进制数据 */ + for (uint32_t j = 0; j < 16; j++) { + if (i + j < len) { + PRINT("%02X ", data[i + j]); + } else { + PRINT(" "); + } + + if (j == 7) { + PRINT(" "); + } + } + + PRINT(" |"); + + /* 打印ASCII字符 */ + for (uint32_t j = 0; j < 16 && i + j < len; j++) { + uint8_t c = data[i + j]; + if (c >= 32 && c <= 126) { + PRINT("%c", c); + } else { + PRINT("."); + } + } + + PRINT("|\r\n"); + } +} + +#else + +void ppp_debug_hexdump(const uint8_t *data, uint32_t len) +{ + (void)data; + (void)len; +} + +#endif /* PPP_DEBUG_ENABLE */ + +/** + * @brief 获取错误码字符串 + */ +const char *ppp_debug_error_string(ppp_error_t error_code) +{ + switch (error_code) { + case PPP_ERR_OK: + return "OK"; + case PPP_ERR_TIMEOUT: + return "TIMEOUT"; + case PPP_ERR_AT_FAILED: + return "AT_FAILED"; + case PPP_ERR_DIAL_FAILED: + return "DIAL_FAILED"; + case PPP_ERR_NO_CARRIER: + return "NO_CARRIER"; + case PPP_ERR_BUSY: + return "BUSY"; + case PPP_ERR_NO_DIALTONE: + return "NO_DIALTONE"; + case PPP_ERR_INVALID_PARAM: + return "INVALID_PARAM"; + case PPP_ERR_MEMORY: + return "MEMORY"; + case PPP_ERR_UART: + return "UART"; + case PPP_ERR_UNKNOWN: + default: + return "UNKNOWN"; + } +} + +/** + * @brief 获取状态字符串 + */ +const char *ppp_debug_state_string(ppp_state_t state) +{ + switch (state) { + case PPP_STATE_IDLE: + return "IDLE"; + case PPP_STATE_INIT: + return "INIT"; + case PPP_STATE_AT_HANDSHAKE: + return "AT_HANDSHAKE"; + case PPP_STATE_SET_BAUDRATE: + return "SET_BAUDRATE"; + case PPP_STATE_DIALING: + return "DIALING"; + case PPP_STATE_CONNECTED: + return "CONNECTED"; + case PPP_STATE_DISCONNECTING: + return "DISCONNECTING"; + case PPP_STATE_ERROR: + return "ERROR"; + default: + return "UNKNOWN"; + } +} \ No newline at end of file diff --git a/code/sdk/zhihui/n706a_net/src/ppp_debug_manager.c b/code/sdk/zhihui/n706a_net/src/ppp_debug_manager.c new file mode 100644 index 000000000..afbc2f112 --- /dev/null +++ b/code/sdk/zhihui/n706a_net/src/ppp_debug_manager.c @@ -0,0 +1,101 @@ +/** + * @file ppp_debug_manager.c + * @brief PPP调试级别动态管理模块 + * + * 提供运行时动态调整PPP调试输出级别的功能 + */ + +#include "ppp_debug_manager.h" +#include "ppp_debug.h" +#include "ppp_config.h" +#include + +/* 全局调试级别变量 */ +static volatile int g_ppp_dynamic_debug_level = PPP_DEBUG_LEVEL; + +/** + * @brief 获取当前动态调试级别 + * @return 当前调试级别 + */ +int ppp_debug_get_level(void) +{ + return g_ppp_dynamic_debug_level; +} + +/** + * @brief 设置动态调试级别 + * @param level 调试级别 (0-4) + * @return PPP_ERR_OK-成功,其他-失败 + */ +ppp_error_t ppp_debug_set_level(int level) +{ + if (level < PPP_DEBUG_LEVEL_NONE || level > PPP_DEBUG_LEVEL_DEBUG) { + return PPP_ERR_INVALID_PARAM; + } + + g_ppp_dynamic_debug_level = level; + return PPP_ERR_OK; +} + +/** + * @brief 获取调试级别名称 + * @param level 调试级别 + * @return 级别名称字符串 + */ +const char* ppp_debug_get_level_name(int level) +{ + switch (level) { + case PPP_DEBUG_LEVEL_NONE: return "NONE"; + case PPP_DEBUG_LEVEL_ERROR: return "ERROR"; + case PPP_DEBUG_LEVEL_WARN: return "WARN"; + case PPP_DEBUG_LEVEL_INFO: return "INFO"; + case PPP_DEBUG_LEVEL_DEBUG: return "DEBUG"; + default: return "UNKNOWN"; + } +} + +/** + * @brief 根据名称设置调试级别 + * @param level_name 级别名称 ("NONE", "ERROR", "WARN", "INFO", "DEBUG") + * @return PPP_ERR_OK-成功,其他-失败 + */ +ppp_error_t ppp_debug_set_level_by_name(const char* level_name) +{ + if (!level_name) { + return PPP_ERR_INVALID_PARAM; + } + + if (strcasecmp(level_name, "NONE") == 0) { + return ppp_debug_set_level(PPP_DEBUG_LEVEL_NONE); + } else if (strcasecmp(level_name, "ERROR") == 0) { + return ppp_debug_set_level(PPP_DEBUG_LEVEL_ERROR); + } else if (strcasecmp(level_name, "WARN") == 0) { + return ppp_debug_set_level(PPP_DEBUG_LEVEL_WARN); + } else if (strcasecmp(level_name, "INFO") == 0) { + return ppp_debug_set_level(PPP_DEBUG_LEVEL_INFO); + } else if (strcasecmp(level_name, "DEBUG") == 0) { + return ppp_debug_set_level(PPP_DEBUG_LEVEL_DEBUG); + } else { + return PPP_ERR_INVALID_PARAM; + } +} + +/** + * @brief 重置调试级别为默认值 + * @return PPP_ERR_OK-成功 + */ +ppp_error_t ppp_debug_reset_level(void) +{ + g_ppp_dynamic_debug_level = PPP_DEBUG_LEVEL; + return PPP_ERR_OK; +} + +/** + * @brief 检查指定级别是否启用 + * @param level 要检查的级别 + * @return true-启用,false-未启用 + */ +bool ppp_debug_is_level_enabled(int level) +{ + return g_ppp_dynamic_debug_level >= level; +} \ No newline at end of file diff --git a/code/sdk/zhihui/n706a_net/src/ppp_interrupt.c b/code/sdk/zhihui/n706a_net/src/ppp_interrupt.c new file mode 100644 index 000000000..b7150ef2c --- /dev/null +++ b/code/sdk/zhihui/n706a_net/src/ppp_interrupt.c @@ -0,0 +1,1597 @@ +/** + * @file ppp_interrupt.c + * @brief UART中断接收处理 - 使用乒乓缓冲区优化 + * + * 核心设计: + * - 使用乒乓缓冲区(多个buffer,每个2KB)避免频繁malloc/free + * - ISR中写入当前buffer,满了自动切换到另一个buffer + * - 通过消息队列传递buffer索引(而非拷贝数据) + * - 定时器监控buffer状态,超时自动flush(30ms) + * - 处理线程负责数据转发: + * * PPP模式:调用pppos_input_tcpip()送入lwIP协议栈 + * * N706A_NET模式:写入AT响应缓冲区供应用层读取 + */ +#include "ppp_interrupt.h" +#include "ppp_config.h" +#include "ppp_debug.h" +#ifndef SUPPORT_N706A_NET +#include "ppp_lwip.h" // 仅PPP模式需要 +#endif +#include "ppp_serial.h" +#include "uart.h" +#include "hal_uart.h" +#include "errcode.h" +#include "osal_task.h" +#include "osal_msgqueue.h" +#include "time/osal_timer.h" +#include "soc_osal.h" +#include "custom_utils.h" // 添加:用于uapi_create_osThread_custom +#include +#include +#include + +/* lwIP相关头文件(仅PPP模式) */ +#ifndef SUPPORT_N706A_NET +#include "lwip/err.h" +#include "lwip/stats.h" +#include "netif/ppp/ppp.h" +#include "netif/ppp/pppos.h" +#endif + +/* 乒乓缓冲区配置 */ +#ifndef PPP_PING_PONG_BUFFER_SIZE +#define PPP_PING_PONG_BUFFER_SIZE 2048 // 每个buffer大小(2KB) +#endif + +#define PPP_PING_PONG_BUFFER_COUNT 4 // 三缓冲模式(改为3个) +#define PPP_PING_PONG_TIMEOUT_MS 50 // buffer超时时间(30ms) + +/* PPP中断消息队列配置 */ +#define PPP_IRQ_MSG_QUEUE_SIZE 16 // 队列深度(乒乓模式下无需太大) +#define PPP_IRQ_PROCESS_TASK_PRIO 24 // 处理线程优先级 +#define PPP_IRQ_PROCESS_STACK_SIZE 4096 // 处理线程栈大小 + +/* 乒乓缓冲区状态 */ +typedef enum { + PPP_BUFFER_STATE_EMPTY = 0, // 空闲,可以写入 + PPP_BUFFER_STATE_FILLING, // 正在填充中 + PPP_BUFFER_STATE_FULL, // 已满或超时,等待处理 + PPP_BUFFER_STATE_PROCESSING // 正在处理中 +} ppp_buffer_state_t; + +/* 乒乓缓冲区结构 */ +typedef struct { + uint8_t data[PPP_PING_PONG_BUFFER_SIZE]; // 数据缓冲区 + volatile uint32_t write_pos; // 当前写入位置 + volatile ppp_buffer_state_t state; // 缓冲区状态 + volatile uint32_t last_write_time; // 最后写入时间戳(ms) + volatile uint32_t first_write_time; // 首次写入时间戳(用于统计填充时间) +} ppp_ping_pong_buffer_t; + +/* PPP中断消息结构 - 通过消息队列传递buffer索引 */ +typedef struct { + uint8_t buffer_index; // 缓冲区索引(0~PPP_PING_PONG_BUFFER_COUNT-1) + uint16_t length; // 数据长度 +} ppp_irq_msg_t; + +/* 中断使能标志 */ +static volatile bool g_ppp_interrupt_enabled = false; + +/* 处理线程相关 */ +// 中断模式全局变量(供 ppp_suspend/resume 访问) +uint32_t g_ppp_irq_process_task = 0; // 改为uint32_t以配合uapi_create_osThread_custom +static unsigned long g_ppp_irq_msg_queue = 0; // 消息队列ID (OSAL使用unsigned long) +volatile bool g_ppp_irq_task_running = false; // 移除 static,供外部访问 + +/* 乒乓缓冲区(动态分配,避免占用DTCM) */ +static ppp_ping_pong_buffer_t *g_ping_pong_buffers = NULL; // 改为指针,通过malloc动态分配 +static volatile uint8_t g_current_buffer_index = 0; // ⚠️ volatile不保证原子性,但单字节写入在ARM上通常是原子的 + +/* 全局时间戳(由定时器更新,ISR只读) */ +static volatile uint32_t g_timer_timestamp_ms = 0; + +/* 定时器相关 */ +// Timer 全局变量(供 ppp_suspend/resume 访问) +osal_timer g_ppp_timeout_timer; // 移除 static +volatile bool g_ppp_timer_running = false; // 移除 static + +/* 统计信息 */ +static ppp_interrupt_stats_t g_ppp_interrupt_stats = {0}; + +/* 扩展统计信息:乒乓缓冲区相关 */ +typedef struct { + uint32_t buffer_full_count; // buffer满次数 + uint32_t buffer_timeout_count; // buffer超时flush次数(未满但超时) + uint32_t buffer_switch_count; // buffer切换次数 + uint32_t dropped_both_full; // 两个buffer都满导致丢弃的字节数 + uint32_t switch_failed_count; // buffer切换失败次数(安全性指标) + uint32_t race_condition_detected; // 检测到的竞态条件次数(诊断) + uint32_t cross_buffer_count; // è·¨buffer写入次数(数据跨两个buffer) + uint32_t timeout_unfilled_count; // 超时时未满的flush次数 + uint32_t fill_time_min_ms; // buffer填充最短时间(ms) + uint32_t fill_time_max_ms; // buffer填充最长时间(ms) + uint32_t fill_time_total_ms; // buffer填充总时间(用于计算平均值) + uint32_t fill_time_count; // buffer填充次数统计 +} ppp_pingpong_stats_t; + +static ppp_pingpong_stats_t g_pingpong_stats = {0}; + +/* UART错误统计信息 */ +typedef struct { + uint32_t parity_error_count; // 奇偶校验错误次数 + uint32_t frame_error_count; // 帧错误次数 + uint32_t last_parity_error_info[4]; // 最近一次奇偶校验错误信息 + uint32_t last_frame_error_info[4]; // 最近一次帧错误信息 +} ppp_uart_error_stats_t; + +static ppp_uart_error_stats_t g_uart_error_stats = {0}; + +/* PPP数据传输状态检测(用于安全执行+++/ATO命令) */ +typedef struct { + uint32_t last_rx_time_ms; // 最后接收数据时间 + uint32_t rx_bytes_this_second; // 本秒接收的字节数 + uint32_t rx_rate_bps; // 接收速率(bytes/sec) +} ppp_data_state_t; + +static ppp_data_state_t g_ppp_data_state = {0}; + +/* 配置参数 */ +#define PPP_SAFE_SWITCH_IDLE_MS 5000 // 5秒空闲才安全切换 +#define PPP_LIGHT_THRESHOLD_BPS 100 // 100 bps以下为轻量传输 + +#ifdef SUPPORT_PPP_SERVICE_INTERRUPT +#define PPP_CAT1_VOTE_IDLE_MS 3000 // 3秒空闲后移除CAT1 vote +/* CAT1 vote 状态管理 */ +static volatile bool g_ppp_cat1_vote_added = false; // CAT1 vote 状态标志 + +#endif + +#ifdef SUPPORT_PPP_SERVICE_INTERRUPT +/* 外部函数声明(CAT1 API) */ +extern void cat1_api_add_cat1_vote(void); +extern void cat1_api_remove_cat1_vote(void); +#endif + +/* 内部函数声明 */ +static uint32_t ppp_get_current_time_ms(void); +static ppp_error_t ppp_flush_buffer(uint8_t buffer_index); +static void ppp_timeout_timer_callback(unsigned long arg); +static void ppp_uart_parity_error_callback(uint32_t *err_info, uint32_t len); +static void ppp_uart_frame_error_callback(uint32_t *err_info, uint32_t len); + +/** + * @brief 获取当前时间戳(毫秒) + */ +static uint32_t ppp_get_current_time_ms(void) +{ + unsigned long long jiffies = osal_get_jiffies(); + return (uint32_t)osal_jiffies_to_msecs((unsigned int)jiffies); +} + +/** + * @brief UART中断数据处理线程(乒乓缓冲区模式) + * @note 从消息队列接收buffer索引,直接访问乒乓缓冲区 + * PPP模式:调用pppos_input_tcpip()送入lwIP + * N706A_NET模式:写入AT响应缓冲区 + */ +static void ppp_irq_process_thread(void *arg) +{ + (void)arg; + ppp_irq_msg_t msg; + unsigned int msg_size = sizeof(ppp_irq_msg_t); + + // ⚠️ 使用 WARN 级别确保能看到此日志,用于诊断线程是否正常启动 +#ifdef SUPPORT_N706A_NET + PPP_DEBUG_WARN(">>> N706A_NET IRQ process thread STARTED (ping-pong mode) <<<"); + PPP_DEBUG_INFO("Thread running, waiting for AT data from ISR..."); +#else + PPP_DEBUG_WARN(">>> PPP IRQ process thread STARTED (ping-pong mode) <<<"); + PPP_DEBUG_INFO("Thread running, waiting for PPP data from ISR..."); +#endif + #if defined(PPP_DO_SERIAL_TEST) && (PPP_DO_SERIAL_TEST == 1) + const uint8_t print_data[] = "PPP IRQ process thread started (ping-pong mode)\n"; + uapi_uart_write(SIO_UART_BUS, print_data, sizeof(print_data), 100); + #endif + while (g_ppp_irq_task_running) { + // 从消息队列读取buffer索引消息(阻塞等待) + int ret = osal_msg_queue_read_copy( + g_ppp_irq_msg_queue, + &msg, + &msg_size, + OSAL_MSGQ_WAIT_FOREVER + ); + + if (ret != OSAL_SUCCESS || !g_ppp_irq_task_running) { + continue; + } + + // 验证buffer索引 + if (msg.buffer_index >= PPP_PING_PONG_BUFFER_COUNT) { + PPP_DEBUG_ERROR("Invalid buffer index: %u", msg.buffer_index); + continue; + } + + // ⚠️ 检查内存是否已分配 + if (g_ping_pong_buffers == NULL) { + PPP_DEBUG_ERROR("Buffer memory not allocated"); + continue; + } + + ppp_ping_pong_buffer_t *buffer = &g_ping_pong_buffers[msg.buffer_index]; + + // 验证数据长度 + if (msg.length == 0 || msg.length > PPP_PING_PONG_BUFFER_SIZE) { + PPP_DEBUG_ERROR("Invalid data length: %u", msg.length); + buffer->state = PPP_BUFFER_STATE_EMPTY; + buffer->write_pos = 0; + continue; + } + + // 标记为处理中 + buffer->state = PPP_BUFFER_STATE_PROCESSING; + + // 打印摘要 + PPP_DEBUG_DEBUG("PPP Process: buffer[%u], %u bytes", msg.buffer_index, msg.length); + + // 只打印前16字节 + PPP_DEBUG_PRINT(" Data: "); + for (uint32_t i = 0; i < msg.length && i < 16; i++) { + PPP_DEBUG_PRINT("%02X ", buffer->data[i]); + } + if (msg.length > 16) { + PPP_DEBUG_PRINT("... (%u more bytes)", msg.length - 16); + } + PPP_DEBUG_PRINT("\n"); + +#if defined(PPP_DO_SERIAL_TEST) && (PPP_DO_SERIAL_TEST == 1) + err_t result = ERR_OK; + uint32_t write_result = uapi_uart_write(SIO_UART_BUS, buffer->data, msg.length, 100); + if (write_result != msg.length) { + PPP_DEBUG_WARN("uapi_uart_write failed: %d, dropped %u bytes", write_result, msg.length); + g_ppp_interrupt_stats.dropped_lwip_error += msg.length; + g_ppp_interrupt_stats.lwip_error_count++; + } else { + PPP_DEBUG_WARN("uapi_uart_write : %u bytes", msg.length); + g_ppp_interrupt_stats.total_bytes_sent_to_lwip += msg.length; + g_ppp_interrupt_stats.success_count++; + } +#else //if defined(SUPPORT_N706A_NET) + // n706a_net模式:写入AT响应缓冲区(而非lwIP) + extern void n706a_at_response_buffer_write(const uint8_t *data, uint32_t length); + n706a_at_response_buffer_write(buffer->data, msg.length); + g_ppp_interrupt_stats.total_bytes_sent_to_lwip += msg.length; + g_ppp_interrupt_stats.success_count++; + PPP_DEBUG_DEBUG("n706a_net: Sent %u bytes to AT response buffer", msg.length); +#endif + // 释放buffer(标记为空闲) + buffer->state = PPP_BUFFER_STATE_EMPTY; + buffer->write_pos = 0; + buffer->first_write_time = 0; // 重置首次写入时间 + } + + PPP_DEBUG_INFO("PPP IRQ process thread exited"); +} + +/** + * @brief 刷新指定buffer到消息队列 + * @param buffer_index 缓冲区索引 + * @param is_timeout 是否因超时触发(用于统计) + * @return PPP_ERR_OK 成功,其他值失败 + */ +static ppp_error_t ppp_flush_buffer_internal(uint8_t buffer_index, bool is_timeout) +{ + if (buffer_index >= PPP_PING_PONG_BUFFER_COUNT) { + return PPP_ERR_INVALID_PARAM; + } + + // ⚠️ 检查内存是否已分配 + if (g_ping_pong_buffers == NULL) { + return PPP_ERR_INVALID_PARAM; + } + + ppp_ping_pong_buffer_t *buffer = &g_ping_pong_buffers[buffer_index]; + + // 只flush FILLING状态的buffer + if (buffer->state != PPP_BUFFER_STATE_FILLING || buffer->write_pos == 0) { + return PPP_ERR_OK; // 没有数据需要flush + } + + // 统计填充时间(从首次写入到flush) + if (buffer->first_write_time > 0) { + uint32_t current_time = ppp_get_current_time_ms(); + uint32_t fill_time = current_time - buffer->first_write_time; + + // 更新最小/最大填充时间 + if (g_pingpong_stats.fill_time_count == 0) { + g_pingpong_stats.fill_time_min_ms = fill_time; + g_pingpong_stats.fill_time_max_ms = fill_time; + } else { + if (fill_time < g_pingpong_stats.fill_time_min_ms) { + g_pingpong_stats.fill_time_min_ms = fill_time; + } + if (fill_time > g_pingpong_stats.fill_time_max_ms) { + g_pingpong_stats.fill_time_max_ms = fill_time; + } + } + g_pingpong_stats.fill_time_total_ms += fill_time; + g_pingpong_stats.fill_time_count++; + } + + // 统计超时且未满的flush + if (is_timeout && buffer->write_pos < PPP_PING_PONG_BUFFER_SIZE) { + g_pingpong_stats.timeout_unfilled_count++; + } + + // 标记为FULL状态 + buffer->state = PPP_BUFFER_STATE_FULL; + + // 准备消息 + ppp_irq_msg_t msg = { + .buffer_index = buffer_index, + .length = (uint16_t)buffer->write_pos + }; + + // 发送到消息队列(非阻塞) + int ret = osal_msg_queue_write_copy( + g_ppp_irq_msg_queue, + &msg, + sizeof(ppp_irq_msg_t), + OSAL_MSGQ_NO_WAIT + ); + + if (ret != OSAL_SUCCESS) { + // 队列满,无法发送,保持FULL状态等待下次重试 + g_ppp_interrupt_stats.dropped_queue_full += buffer->write_pos; + return PPP_ERR_UNKNOWN; + } + + return PPP_ERR_OK; +} + +// 便利函数:非超时flush +static ppp_error_t ppp_flush_buffer(uint8_t buffer_index) +{ + return ppp_flush_buffer_internal(buffer_index, false); +} + +/** + * @brief 定时器回调:检查buffer超时并flush + * @note 定时器上下文,应快速返回 + * @param arg 定时器参数(未使用) + * @warning osal_timer默认是one-shot,必须在回调中手动重启实现周期性执行 + * + * 数据安全设计(避免竞态): + * - 定时器职责:只检查和flush **非当前** buffer(避免与ISR竞争) + * - ISR 职责: + * 1. 在写入前检查当前buffer是否超时(last_write_time == 0),超时则flush+切换 + * 2. 检查当前buffer状态,如果不可写则切换 + * 3. 独占 g_current_buffer_index 的修改权 + * - 分工明确:定时器不操作当前buffer,ISR负责当前buffer的超时处理 + * - 数据保证:disable时强制flush所有buffer,确保数据不丢失 + */ +static void ppp_timeout_timer_callback(unsigned long arg) +{ + (void)arg; + + // 增加定时器调用计数(用于诊断) + static volatile uint32_t timer_call_count = 0; + timer_call_count++; + + // 每100次(约3秒)打印一次诊断信息 + if ((timer_call_count % 100) == 1) { + PPP_DEBUG_INFO("[TIMER] Callback executed %u times, enabled=%d, running=%d", + timer_call_count, g_ppp_interrupt_enabled, g_ppp_timer_running); + } + + // 每333次(约10秒)打印一次PPP丢失统计 + // 30ms * 333 ≈ 10秒 + if ((timer_call_count % 333) == 0) { + ppp_interrupt_print_stats(); + } + + if (!g_ppp_interrupt_enabled || !g_ppp_timer_running) { + return; + } + + uint32_t current_time = ppp_get_current_time_ms(); + + // ⚠️ 更新全局时间戳(供ISR使用,ISR中不能调用时间函数) + g_timer_timestamp_ms = current_time; + + // ✅ 每秒更新一次接收速率(用于安全执行+++/ATO命令) + static uint32_t last_rate_update_time = 0; + if (last_rate_update_time == 0) { + last_rate_update_time = current_time; + } + + uint32_t elapsed = current_time - last_rate_update_time; + if (elapsed >= 1000) { // 每秒更新 + g_ppp_data_state.rx_rate_bps = + (g_ppp_data_state.rx_bytes_this_second * 1000) / elapsed; + g_ppp_data_state.rx_bytes_this_second = 0; + last_rate_update_time = current_time; + } + +#ifdef SUPPORT_PPP_SERVICE_INTERRUPT + // ✅ CAT1 vote 管理:检测 idle 状态,超过阈值时移除 vote + if (g_ppp_cat1_vote_added) { + if (g_ppp_data_state.last_rx_time_ms > 0) { + uint32_t idle_time = current_time - g_ppp_data_state.last_rx_time_ms; + if (idle_time >= PPP_CAT1_VOTE_IDLE_MS) { + // 空闲时间超过阈值,移除 vote 允许 CAT1 休眠 + cat1_api_remove_cat1_vote(); + g_ppp_cat1_vote_added = false; + PPP_DEBUG_INFO("[CAT1_VOTE] Removed vote after %ums idle", idle_time); + } + } + } +#endif + + uint8_t current_idx = g_current_buffer_index; // 读取一次,避免重复读取 + + // ⚠️ 检查内存是否已分配 + if (g_ping_pong_buffers == NULL) { + return; + } + + // ⚠️ 检查所有buffer的超时状态并按写入顺序 flush + // ⚠️ 关键修复:必须按 first_write_time 排序,避免数据乱序 + + // 第1步:更新时间戳并收集超时的 buffer + typedef struct { + uint8_t index; + uint32_t write_time; + bool is_current; + } buffer_order_t; + + buffer_order_t buffers_to_flush[PPP_PING_PONG_BUFFER_COUNT]; + uint8_t flush_count = 0; + + for (uint8_t i = 0; i < PPP_PING_PONG_BUFFER_COUNT; i++) { + // ⚠️ 竞态检测:在处理过程中再次检查current_idx是否变化 + uint8_t current_check = g_current_buffer_index; + bool is_current = (i == current_check); + + // 检测到current_idx在循环中变化(竞态) + if (!is_current && i == current_idx) { + g_pingpong_stats.race_condition_detected++; + continue; // 跳过,避免竞态 + } + + ppp_ping_pong_buffer_t *buffer = &g_ping_pong_buffers[i]; + + // 只处理FILLING状态且有数据的buffer + if (buffer->state == PPP_BUFFER_STATE_FILLING && buffer->write_pos > 0) { + // last_write_time == 0 表示ISR刚写入数据,需要更新时间戳 + if (buffer->last_write_time == 0) { + buffer->last_write_time = current_time; + PPP_DEBUG_DEBUG("[TIMER] Update timestamp for buffer[%u]", i); + } else { + // 检查是否超时 + uint32_t elapsed = current_time - buffer->last_write_time; + + if (elapsed >= PPP_PING_PONG_TIMEOUT_MS) { + // 记录需要 flush 的 buffer + buffers_to_flush[flush_count].index = i; + buffers_to_flush[flush_count].write_time = buffer->first_write_time; + buffers_to_flush[flush_count].is_current = is_current; + flush_count++; + + PPP_DEBUG_DEBUG("[TIMER] Buffer[%u] timeout (%u ms), %s, %u bytes", + i, elapsed, + is_current ? "CURRENT" : "non-current", + buffer->write_pos); + } + } + } + } + + // 第2步:按 first_write_time 排序 + for (uint8_t i = 0; i < flush_count; i++) { + for (uint8_t j = i + 1; j < flush_count; j++) { + if (buffers_to_flush[j].write_time < buffers_to_flush[i].write_time) { + buffer_order_t temp = buffers_to_flush[i]; + buffers_to_flush[i] = buffers_to_flush[j]; + buffers_to_flush[j] = temp; + } + } + } + + // 第3步:按排序后的顺序 flush + for (uint8_t i = 0; i < flush_count; i++) { + uint8_t idx = buffers_to_flush[i].index; + + PPP_DEBUG_DEBUG("[TIMER] Flushing buffer[%u] (write_order=%u) due to timeout", idx, i); + + // 使用internal函数,标记为超时flush + if (ppp_flush_buffer_internal(idx, true) == PPP_ERR_OK) { + g_pingpong_stats.buffer_timeout_count++; + + // 如果是当前buffer超时被flush,需要切换到下一个 + if (buffers_to_flush[i].is_current) { + uint8_t next_idx = (idx + 1) % PPP_PING_PONG_BUFFER_COUNT; + ppp_ping_pong_buffer_t *next_buffer = &g_ping_pong_buffers[next_idx]; + + if (next_buffer->state == PPP_BUFFER_STATE_EMPTY) { + g_current_buffer_index = next_idx; + g_pingpong_stats.buffer_switch_count++; + PPP_DEBUG_INFO("[TIMER] Switched from buffer[%u] to buffer[%u] after timeout", idx, next_idx); + } + } + } + } + + // ⚠️ 关键修复:osal_timer是one-shot类型,必须手动重启以实现周期性执行 + // 不重启的话,定时器只会执行一次! + if (g_ppp_timer_running) { + osal_timer_start(&g_ppp_timeout_timer); + } +} + +/** + * @brief UART奇偶校验错误回调函数 + * @param err_info 错误信息数组 + * @param len 错误信息数组长度 + * @note 在中断上下文中执行,必须快速返回 + */ +static void ppp_uart_parity_error_callback(uint32_t *err_info, uint32_t len) +{ + g_uart_error_stats.parity_error_count++; + + // 保存错误信息用于诊断(只保存前4个) + if (err_info && len > 0) { + uint32_t copy_len = (len > 4) ? 4 : len; + for (uint32_t i = 0; i < copy_len; i++) { + g_uart_error_stats.last_parity_error_info[i] = err_info[i]; + } + } + + // ⚠️ 中断中禁止打印日志,只统计 +} + +/** + * @brief UART帧错误回调函数 + * @param err_info 错误信息数组 + * @param len 错误信息数组长度 + * @note 在中断上下文中执行,必须快速返回 + */ +static void ppp_uart_frame_error_callback(uint32_t *err_info, uint32_t len) +{ + g_uart_error_stats.frame_error_count++; + + // 保存错误信息用于诊断(只保存前4个) + if (err_info && len > 0) { + uint32_t copy_len = (len > 4) ? 4 : len; + for (uint32_t i = 0; i < copy_len; i++) { + g_uart_error_stats.last_frame_error_info[i] = err_info[i]; + } + } + + // ⚠️ 中断中禁止打印日志,只统计 +} + +/** + * @brief UART接收中断回调函数(乒乓缓冲区模式) + * @note 中断上下文,必须极快返回 + * + * 优化策略: + * 1. 写入当前buffer(memcpy,~60字节 < 1μs) + * 2. buffer满了直接flush并切换(~2μs) + * 3. 无malloc/free,减少内存碎片和延迟 + * 4. ⚠️ 新增:跨buffer写入支持(需求1) + * 5. 总耗时目标:< 5μs + */ +void _ppp_uart_rx_interrupt_handler(const void *buffer, uint16_t length, bool error) +{ + // ⚠️ 中断中禁止打印!必须极快返回 + + // 快速路径:参数校验(异常情况也不打印,只统计) + if (!buffer || length == 0 || length > 200 || g_ping_pong_buffers == NULL) { + // 不打印!在中断中打印会阻塞系统,只记录统计 + g_ppp_interrupt_stats.error_count++; + return; + } + if (error) { + g_ppp_interrupt_stats.error_count++; + } + + // 更新统计 + g_ppp_interrupt_stats.total_interrupts++; + g_ppp_interrupt_stats.total_bytes_received += length; + + // ✅ 更新状态检测(用于安全执行+++/ATO命令) + g_ppp_data_state.last_rx_time_ms = g_timer_timestamp_ms; + g_ppp_data_state.rx_bytes_this_second += length; + +#ifdef SUPPORT_PPP_SERVICE_INTERRUPT + // ✅ CAT1 vote 管理:接收数据时添加 vote(保持 CAT1 唤醒) + if (!g_ppp_cat1_vote_added) { + cat1_api_add_cat1_vote(); + g_ppp_cat1_vote_added = true; + } +#endif + + // 获取当前buffer + uint8_t current_idx = g_current_buffer_index; + ppp_ping_pong_buffer_t *current_buffer = &g_ping_pong_buffers[current_idx]; + + // ⚠️ 检查当前buffer的超时 + if (current_buffer->state == PPP_BUFFER_STATE_FILLING && + current_buffer->write_pos > 0 && + current_buffer->last_write_time != 0) { + uint32_t current_time = g_timer_timestamp_ms; + uint32_t elapsed = current_time - current_buffer->last_write_time; + + if (elapsed >= PPP_PING_PONG_TIMEOUT_MS) { + // 超时,flush并切换 + ppp_flush_buffer(current_idx); + g_pingpong_stats.buffer_timeout_count++; + + uint8_t next_idx = (current_idx + 1) % PPP_PING_PONG_BUFFER_COUNT; + if (g_ping_pong_buffers[next_idx].state == PPP_BUFFER_STATE_EMPTY) { + g_current_buffer_index = next_idx; + g_pingpong_stats.buffer_switch_count++; + current_idx = next_idx; + current_buffer = &g_ping_pong_buffers[current_idx]; + } + } + } + + // 如果当前buffer不可写,尝试切换 + if (current_buffer->state != PPP_BUFFER_STATE_EMPTY && + current_buffer->state != PPP_BUFFER_STATE_FILLING) { + uint8_t next_idx = (current_idx + 1) % PPP_PING_PONG_BUFFER_COUNT; + if (g_ping_pong_buffers[next_idx].state == PPP_BUFFER_STATE_EMPTY) { + g_current_buffer_index = next_idx; + g_pingpong_stats.buffer_switch_count++; + current_idx = next_idx; + current_buffer = &g_ping_pong_buffers[current_idx]; + } + } + + // 初始化空buffer + if (current_buffer->state == PPP_BUFFER_STATE_EMPTY) { + current_buffer->state = PPP_BUFFER_STATE_FILLING; + current_buffer->write_pos = 0; + current_buffer->first_write_time = g_timer_timestamp_ms; // ⚠️ 需求3:记录首次写入时间 + } + + // 计算当前buffer剩余空间 + uint32_t available = PPP_PING_PONG_BUFFER_SIZE - current_buffer->write_pos; + uint16_t remaining_data = length; + const uint8_t *data_ptr = (const uint8_t *)buffer; + + // ⚠️ 需求1:跨buffer写入处理 + if (current_buffer->state == PPP_BUFFER_STATE_FILLING) { + if (remaining_data <= available) { + // 情况1:数据完全放得下,直接写入 + memcpy(current_buffer->data + current_buffer->write_pos, data_ptr, remaining_data); + current_buffer->write_pos += remaining_data; + current_buffer->last_write_time = 0; // 由定时器更新 + + // 检查是否正好填满 + if (current_buffer->write_pos >= PPP_PING_PONG_BUFFER_SIZE) { + ppp_flush_buffer(current_idx); + g_pingpong_stats.buffer_full_count++; + + // 切换buffer + uint8_t next_idx = (current_idx + 1) % PPP_PING_PONG_BUFFER_COUNT; + if (g_ping_pong_buffers[next_idx].state == PPP_BUFFER_STATE_EMPTY) { + g_current_buffer_index = next_idx; + g_pingpong_stats.buffer_switch_count++; + } else { + g_pingpong_stats.switch_failed_count++; + } + } + } else { + // 情况2:数据超过当前buffer剩余空间,需要跨buffer写入 + g_pingpong_stats.cross_buffer_count++; // ⚠️ 统计跨buffer次数 + + // 第1步:先填满当前buffer + if (available > 0) { + memcpy(current_buffer->data + current_buffer->write_pos, data_ptr, available); + current_buffer->write_pos += available; + data_ptr += available; + remaining_data -= available; + } + + // 第2步:flush当前buffer + ppp_flush_buffer(current_idx); + g_pingpong_stats.buffer_full_count++; + + // 第3步:切换到下一个buffer + uint8_t next_idx = (current_idx + 1) % PPP_PING_PONG_BUFFER_COUNT; + ppp_ping_pong_buffer_t *next_buffer = &g_ping_pong_buffers[next_idx]; + + if (next_buffer->state == PPP_BUFFER_STATE_EMPTY && + remaining_data <= PPP_PING_PONG_BUFFER_SIZE) { + // 第4步:把剩余数据写入新buffer + g_current_buffer_index = next_idx; + g_pingpong_stats.buffer_switch_count++; + + next_buffer->state = PPP_BUFFER_STATE_FILLING; + next_buffer->write_pos = 0; + next_buffer->first_write_time = g_timer_timestamp_ms; // 新buffer的首次写入时间 + + memcpy(next_buffer->data, data_ptr, remaining_data); + next_buffer->write_pos = remaining_data; + next_buffer->last_write_time = 0; // 由定时器更新 + } else { + // 下一个buffer不可用,剩余数据丢弃 + g_pingpong_stats.dropped_both_full += remaining_data; + g_pingpong_stats.switch_failed_count++; + } + } + } else { + // 当前buffer不可用,数据丢弃 + g_pingpong_stats.dropped_both_full += length; + } +} + +void ppp_uart_rx_interrupt_handler(const void *buffer, uint16_t length, bool error) +{ + _ppp_uart_rx_interrupt_handler(buffer, length, error); + hal_uart_clear_pending(UART_BUS_0); +} + +/** + * @brief 启用PPP UART中断接收(乒乓缓冲区模式) + * @return PPP_ERR_OK 成功,其他值失败 + */ +ppp_error_t ppp_interrupt_enable(void) +{ + if (g_ppp_interrupt_enabled) { + PPP_DEBUG_WARN("PPP interrupt already enabled"); + return PPP_ERR_OK; + } + + PPP_DEBUG_INFO("Enabling PPP UART interrupt mode (ping-pong buffer)"); + + // 1. 动态分配乒乓缓冲区内存 + if (g_ping_pong_buffers != NULL) { + PPP_DEBUG_ERROR("Memory leak detected: g_ping_pong_buffers is not NULL!"); + free(g_ping_pong_buffers); + g_ping_pong_buffers = NULL; + } + + size_t buffer_size = sizeof(ppp_ping_pong_buffer_t) * PPP_PING_PONG_BUFFER_COUNT; + g_ping_pong_buffers = (ppp_ping_pong_buffer_t *)malloc(buffer_size); + + if (g_ping_pong_buffers == NULL) { + PPP_DEBUG_ERROR("Failed to allocate %zu bytes for ping-pong buffers", buffer_size); + return PPP_ERR_MEMORY; + } + + PPP_DEBUG_INFO("Allocated %zu bytes (%.2f KB) for ping-pong buffers via malloc", + buffer_size, buffer_size / 1024.0); + + // 2. 初始化乒乓缓冲区 + for (uint8_t i = 0; i < PPP_PING_PONG_BUFFER_COUNT; i++) { + g_ping_pong_buffers[i].write_pos = 0; + g_ping_pong_buffers[i].state = PPP_BUFFER_STATE_EMPTY; + g_ping_pong_buffers[i].last_write_time = 0; + g_ping_pong_buffers[i].first_write_time = 0; + memset(g_ping_pong_buffers[i].data, 0, PPP_PING_PONG_BUFFER_SIZE); + } + g_current_buffer_index = 0; + +#ifdef SUPPORT_PPP_SERVICE_INTERRUPT + // 2.1 初始化 CAT1 vote 状态 + g_ppp_cat1_vote_added = false; +#endif + + PPP_DEBUG_INFO("Ping-pong buffers initialized: %d x %d bytes = %d KB total", + PPP_PING_PONG_BUFFER_COUNT, PPP_PING_PONG_BUFFER_SIZE, + (PPP_PING_PONG_BUFFER_COUNT * PPP_PING_PONG_BUFFER_SIZE) / 1024); + + // 3. 创建消息队列(只传递buffer索引,所以队列可以更小) + int ret = osal_msg_queue_create( + "ppp_irq_queue", + PPP_IRQ_MSG_QUEUE_SIZE, + &g_ppp_irq_msg_queue, + 0, + sizeof(ppp_irq_msg_t) + ); + + if (ret != OSAL_SUCCESS) { + PPP_DEBUG_ERROR("Failed to create message queue: 0x%x", ret); + free(g_ping_pong_buffers); + g_ping_pong_buffers = NULL; + return PPP_ERR_MEMORY; + } + + PPP_DEBUG_INFO("Message queue created (depth=%d, item_size=%d bytes)", + PPP_IRQ_MSG_QUEUE_SIZE, sizeof(ppp_irq_msg_t)); + + // 4. 启动处理线程(使用 uapi_create_osThread_custom 在 PSRAM 中分配栈) + // ✅ 优化:通过 USE_EXTEND_MEM 将线程栈分配到 PSRAM,节省珍贵的 DTCM + g_ppp_irq_task_running = true; + + app_task_definition_t irq_task_cfg = {0}; + irq_task_cfg.attr.name = "ppp_irq_proc"; + irq_task_cfg.attr.priority = PPP_IRQ_PROCESS_TASK_PRIO; // 优先级24(对应原MIDDLE) + irq_task_cfg.attr.stack_size = PPP_IRQ_PROCESS_STACK_SIZE; + irq_task_cfg.func = (osThreadFunc_t)ppp_irq_process_thread; + irq_task_cfg.task_handle = &g_ppp_irq_process_task; + irq_task_cfg.use_mem = USE_EXTEND_MEM; // ⭐ 关键:使用 PSRAM 分配栈 + irq_task_cfg.create_queue_handle = NULL; + + ret = uapi_create_osThread_custom(&irq_task_cfg); + if (ret != ERRCODE_SUCC) { + PPP_DEBUG_ERROR("Failed to create IRQ process thread: 0x%x", ret); + osal_msg_queue_delete(g_ppp_irq_msg_queue); + g_ppp_irq_msg_queue = 0; + g_ppp_irq_task_running = false; + free(g_ping_pong_buffers); + g_ping_pong_buffers = NULL; + return PPP_ERR_MEMORY; + } + + PPP_DEBUG_WARN("IRQ process thread created with PSRAM stack (handle=0x%X)", + g_ppp_irq_process_task); + PPP_DEBUG_INFO("Process thread: stack=%d bytes in PSRAM, priority=Normal(24)", + PPP_IRQ_PROCESS_STACK_SIZE); + + // 5. 启动定时器(30ms周期检查buffer超时) + // 先设置timer的handler、data和interval + g_ppp_timeout_timer.handler = ppp_timeout_timer_callback; + g_ppp_timeout_timer.data = 0; + g_ppp_timeout_timer.interval = PPP_PING_PONG_TIMEOUT_MS; + + ret = osal_timer_init(&g_ppp_timeout_timer); + if (ret != OSAL_SUCCESS) { + PPP_DEBUG_ERROR("Failed to init timeout timer: 0x%x", ret); + g_ppp_irq_task_running = false; + osal_msleep(100); + osal_msg_queue_delete(g_ppp_irq_msg_queue); + g_ppp_irq_msg_queue = 0; + free(g_ping_pong_buffers); + g_ping_pong_buffers = NULL; + return PPP_ERR_MEMORY; + } + + g_ppp_timer_running = true; + ret = osal_timer_start(&g_ppp_timeout_timer); + if (ret != OSAL_SUCCESS) { + PPP_DEBUG_ERROR("Failed to start timeout timer: 0x%x", ret); + g_ppp_timer_running = false; + g_ppp_irq_task_running = false; + osal_timer_destroy(&g_ppp_timeout_timer); + osal_msleep(100); + osal_msg_queue_delete(g_ppp_irq_msg_queue); + g_ppp_irq_msg_queue = 0; + free(g_ping_pong_buffers); + g_ping_pong_buffers = NULL; + return PPP_ERR_MEMORY; + } + + PPP_DEBUG_INFO("Timeout timer started (period=%d ms)", PPP_PING_PONG_TIMEOUT_MS); + + // 6. 注册UART中断回调 + uint8_t uart_bus = ppp_serial_get_uart_bus(); + + PPP_DEBUG_INFO("Registering UART RX callback:"); + PPP_DEBUG_INFO(" UART Bus: %d", uart_bus); + PPP_DEBUG_INFO(" Condition: 0x07 (FULL_OR_SUFFICIENT_DATA_OR_IDLE)"); + PPP_DEBUG_INFO(" Threshold: 8 bytes"); + PPP_DEBUG_INFO(" Callback: ppp_uart_rx_interrupt_handler @ %p", ppp_uart_rx_interrupt_handler); + + // ⚠️ 阈值说明: + // - 参数是直接的字节数(不是编码) + // - 8字节阈值确保小数据包能触发(PPP协商包通常30-50字节) + // - 配合IDLE条件,即使不满8字节也会在数据停止时触发 + ret = uapi_uart_register_rx_callback( + uart_bus, + 0x07, // UART_RX_CONDITION_FULL_OR_SUFFICIENT_DATA_OR_IDLE + 8, // 阈值:8字节 + ppp_uart_rx_interrupt_handler + ); + + PPP_DEBUG_WARN("uapi_uart_register_rx_callback returned: 0x%x (ERRCODE_SUCC=0x%x)", + ret, ERRCODE_SUCC); + + if (ret != ERRCODE_SUCC) { + PPP_DEBUG_ERROR("Failed to register UART RX callback: 0x%x", ret); + + // 清理资源 + g_ppp_timer_running = false; + osal_timer_stop(&g_ppp_timeout_timer); + osal_timer_destroy(&g_ppp_timeout_timer); + g_ppp_irq_task_running = false; + osal_msleep(100); + osal_msg_queue_delete(g_ppp_irq_msg_queue); + g_ppp_irq_msg_queue = 0; + free(g_ping_pong_buffers); + g_ping_pong_buffers = NULL; + + return PPP_ERR_UART; + } + + PPP_DEBUG_INFO("UART RX callback registered successfully!"); + + // 7. 注册UART错误回调(用于详细错误诊断) + // 7.1 注册奇偶校验错误回调 + ret = uapi_uart_register_parity_error_callback(uart_bus, ppp_uart_parity_error_callback); + if (ret != ERRCODE_SUCC) { + PPP_DEBUG_WARN("Failed to register parity error callback: 0x%x (non-critical)", ret); + // 不是关键错误,继续运行 + } else { + PPP_DEBUG_INFO("Parity error callback registered"); + } + + // 7.2 注册帧错误回调 + ret = uapi_uart_register_frame_error_callback(uart_bus, ppp_uart_frame_error_callback); + if (ret != ERRCODE_SUCC) { + PPP_DEBUG_WARN("Failed to register frame error callback: 0x%x (non-critical)", ret); + // 不是关键错误,继续运行 + } else { + PPP_DEBUG_INFO("Frame error callback registered"); + } + + g_ppp_interrupt_enabled = true; + + PPP_DEBUG_WARN("PPP UART interrupt enabled successfully (ping-pong mode)"); + PPP_DEBUG_WARN(" Ping-pong buffers: %d x %d KB", + PPP_PING_PONG_BUFFER_COUNT, PPP_PING_PONG_BUFFER_SIZE / 1024); + PPP_DEBUG_WARN(" Timeout: %d ms", PPP_PING_PONG_TIMEOUT_MS); + PPP_DEBUG_WARN(" Message queue: %d slots (only %d bytes per message)", + PPP_IRQ_MSG_QUEUE_SIZE, sizeof(ppp_irq_msg_t)); + PPP_DEBUG_INFO(" ISR latency: <5μs (memcpy + queue_write, no malloc!)"); +#ifdef SUPPORT_N706A_NET + PPP_DEBUG_INFO(" Data flow: UART IRQ -> ping-pong buffer -> timer/full flush -> msg_queue -> process_thread -> AT response buffer"); +#else + PPP_DEBUG_INFO(" Data flow: UART IRQ -> ping-pong buffer -> timer/full flush -> msg_queue -> process_thread -> pppos_input_tcpip"); +#endif + PPP_DEBUG_INFO(" DIAGNOSTIC: Watch for [TIMER] and interrupt stats in ppp_interrupt_print_stats()"); + + return PPP_ERR_OK; +} + +/** + * @brief 禁用PPP UART中断接收 + */ +ppp_error_t ppp_interrupt_disable(void) +{ + if (!g_ppp_interrupt_enabled) { + return PPP_ERR_OK; + } + + PPP_DEBUG_INFO("Disabling PPP UART interrupt (ping-pong mode)"); + + // 1. 注销UART中断回调 + uint8_t uart_bus = ppp_serial_get_uart_bus(); + uapi_uart_unregister_rx_callback(uart_bus); + + // 2. 停止定时器 + if (g_ppp_timer_running) { + g_ppp_timer_running = false; + osal_timer_stop(&g_ppp_timeout_timer); + osal_timer_destroy(&g_ppp_timeout_timer); + + // ⚠️ 关键修复:清零 timer 指针,否则下次 osal_timer_init 会因为 timer != NULL 而失败 + // osal_timer_destroy 不会清零 timer->timer,但 osal_timer_init 要求它必须为 NULL + g_ppp_timeout_timer.timer = NULL; + + PPP_DEBUG_INFO("Timeout timer stopped and cleared"); + } + + // 3. 停止处理线程 + g_ppp_irq_task_running = false; + + // 发送一个空消息以唤醒线程(如果阻塞在队列读取) + ppp_irq_msg_t empty_msg = {0}; + osal_msg_queue_write_copy(g_ppp_irq_msg_queue, &empty_msg, + sizeof(ppp_irq_msg_t), OSAL_MSGQ_NO_WAIT); + + // 等待线程退出 + osal_msleep(200); + + // 4. 删除消息队列 + osal_msg_queue_delete(g_ppp_irq_msg_queue); + + // ⚠️ 关键修复:清零队列句柄,否则下次 osal_msg_queue_create 会因为队列句柄非0而失败或创建新队列 + // osal_msg_queue_delete 不会清零队列句柄,但下次 create 需要它为 0 + g_ppp_irq_msg_queue = 0; + + // 5. ⚠️ 数据安全:在清空前,先按写入顺序flush所有有数据的buffer,确保数据不丢失 + if (g_ping_pong_buffers != NULL) { + PPP_DEBUG_INFO("Flushing all buffers in write order before disable..."); + + // 收集并排序(与 pause 相同的逻辑,确保顺序正确) + typedef struct { + uint8_t index; + uint32_t write_time; + } buffer_order_t; + + buffer_order_t buffers_to_flush[PPP_PING_PONG_BUFFER_COUNT]; + uint8_t flush_count = 0; + + for (uint8_t i = 0; i < PPP_PING_PONG_BUFFER_COUNT; i++) { + if (g_ping_pong_buffers[i].state == PPP_BUFFER_STATE_FILLING && + g_ping_pong_buffers[i].write_pos > 0) { + buffers_to_flush[flush_count].index = i; + buffers_to_flush[flush_count].write_time = g_ping_pong_buffers[i].first_write_time; + flush_count++; + } + } + + // 按 first_write_time 排序 + for (uint8_t i = 0; i < flush_count; i++) { + for (uint8_t j = i + 1; j < flush_count; j++) { + if (buffers_to_flush[j].write_time < buffers_to_flush[i].write_time) { + buffer_order_t temp = buffers_to_flush[i]; + buffers_to_flush[i] = buffers_to_flush[j]; + buffers_to_flush[j] = temp; + } + } + } + + // 按正确顺序 flush + for (uint8_t i = 0; i < flush_count; i++) { + uint8_t idx = buffers_to_flush[i].index; + PPP_DEBUG_INFO(" Flushing buffer[%u] (write_order=%u): %u bytes", + idx, i, g_ping_pong_buffers[idx].write_pos); + ppp_flush_buffer(idx); + // 给处理线程一点时间处理最后的数据 + osal_msleep(50); + } + } + + // 6. 清空乒乓缓冲区 + if (g_ping_pong_buffers != NULL) { + for (uint8_t i = 0; i < PPP_PING_PONG_BUFFER_COUNT; i++) { + g_ping_pong_buffers[i].write_pos = 0; + g_ping_pong_buffers[i].state = PPP_BUFFER_STATE_EMPTY; + g_ping_pong_buffers[i].last_write_time = 0; + } + } + g_current_buffer_index = 0; + + // 7. 释放动态分配的内存 + if (g_ping_pong_buffers != NULL) { + size_t buffer_size = sizeof(ppp_ping_pong_buffer_t) * PPP_PING_PONG_BUFFER_COUNT; + PPP_DEBUG_INFO("Freeing %zu bytes (%.2f KB) of ping-pong buffer memory", + buffer_size, buffer_size / 1024.0); + free(g_ping_pong_buffers); + g_ping_pong_buffers = NULL; + } + + // 8. 清零统计信息(为下次启用准备) + memset(&g_ppp_interrupt_stats, 0, sizeof(ppp_interrupt_stats_t)); + memset(&g_pingpong_stats, 0, sizeof(ppp_pingpong_stats_t)); + memset(&g_uart_error_stats, 0, sizeof(ppp_uart_error_stats_t)); + memset(&g_ppp_data_state, 0, sizeof(ppp_data_state_t)); + +#ifdef SUPPORT_PPP_SERVICE_INTERRUPT + // 9. 清理 CAT1 vote(如果之前添加过) + if (g_ppp_cat1_vote_added) { + cat1_api_remove_cat1_vote(); + g_ppp_cat1_vote_added = false; + PPP_DEBUG_INFO("CAT1 vote removed during PPP interrupt disable"); + } +#endif + + g_ppp_interrupt_enabled = false; + g_ppp_irq_process_task = 0; + + PPP_DEBUG_INFO("PPP UART interrupt disabled (memory freed)"); + + return PPP_ERR_OK; +} + +/** + * @brief 检查中断是否已启用 + */ +bool ppp_interrupt_is_enabled(void) +{ + return g_ppp_interrupt_enabled; +} + +/** + * @brief 获取中断统计信息 + */ +void ppp_interrupt_get_stats(ppp_interrupt_stats_t *stats) +{ + if (stats) { + memcpy(stats, &g_ppp_interrupt_stats, sizeof(ppp_interrupt_stats_t)); + } +} + +/** + * @brief 重置中断统计信息 + */ +void ppp_interrupt_reset_stats(void) +{ + memset(&g_ppp_interrupt_stats, 0, sizeof(ppp_interrupt_stats_t)); + memset(&g_pingpong_stats, 0, sizeof(ppp_pingpong_stats_t)); + memset(&g_uart_error_stats, 0, sizeof(ppp_uart_error_stats_t)); + PPP_DEBUG_INFO("PPP interrupt statistics reset (including ping-pong and UART error stats)"); +} + +/** + * @brief 打印中断统计信息(乒乓缓冲区模式) + */ +void ppp_interrupt_print_stats(void) +{ + PPP_DEBUG_WARN("=== PPP Interrupt Statistics (Ping-Pong Mode) ==="); + PPP_DEBUG_WARN("Interrupt Status: %s", g_ppp_interrupt_enabled ? "ENABLED" : "DISABLED"); + PPP_DEBUG_WARN("Ping-Pong Buffers: %d x %d bytes = %d KB total", + PPP_PING_PONG_BUFFER_COUNT, PPP_PING_PONG_BUFFER_SIZE, + (PPP_PING_PONG_BUFFER_COUNT * PPP_PING_PONG_BUFFER_SIZE) / 1024); + PPP_DEBUG_WARN("Timeout: %d ms", PPP_PING_PONG_TIMEOUT_MS); + PPP_DEBUG_WARN("Message Queue: %d slots (%d bytes per message)", + PPP_IRQ_MSG_QUEUE_SIZE, sizeof(ppp_irq_msg_t)); + PPP_DEBUG_WARN(""); + + // 乒乓缓冲区状态 + PPP_DEBUG_WARN("Buffer Status:"); + if (g_ping_pong_buffers != NULL) { + for (uint8_t i = 0; i < PPP_PING_PONG_BUFFER_COUNT; i++) { + const char *state_str = "UNKNOWN"; + switch (g_ping_pong_buffers[i].state) { + case PPP_BUFFER_STATE_EMPTY: state_str = "EMPTY"; break; + case PPP_BUFFER_STATE_FILLING: state_str = "FILLING"; break; + case PPP_BUFFER_STATE_FULL: state_str = "FULL"; break; + case PPP_BUFFER_STATE_PROCESSING: state_str = "PROCESSING"; break; + } + PPP_DEBUG_WARN(" Buffer[%u]: %s, %u/%u bytes%s", + i, state_str, g_ping_pong_buffers[i].write_pos, PPP_PING_PONG_BUFFER_SIZE, + (i == g_current_buffer_index) ? " (current)" : ""); + } + } else { + PPP_DEBUG_WARN(" Buffers not allocated"); + } + PPP_DEBUG_WARN(""); + + // 乒乓缓冲区统计 + PPP_DEBUG_WARN("Ping-Pong Buffer Statistics:"); + PPP_DEBUG_WARN(" Buffer Full Count: %u", g_pingpong_stats.buffer_full_count); + PPP_DEBUG_WARN(" Buffer Timeout Count: %u (total)", g_pingpong_stats.buffer_timeout_count); + PPP_DEBUG_WARN(" - Timeout Unfilled: %u ⚠️ (未满即超时)", g_pingpong_stats.timeout_unfilled_count); + PPP_DEBUG_WARN(" Buffer Switch Count: %u (success)", g_pingpong_stats.buffer_switch_count); + PPP_DEBUG_WARN(" Buffer Switch Failed: %u ⚠️", g_pingpong_stats.switch_failed_count); + PPP_DEBUG_WARN(" Cross-Buffer Writes: %u 📌 (è·¨buffer写入)", g_pingpong_stats.cross_buffer_count); + PPP_DEBUG_WARN(" Dropped (Both Full): %u bytes ⚠️", g_pingpong_stats.dropped_both_full); + + // 填充时间统计 + if (g_pingpong_stats.fill_time_count > 0) { + uint32_t avg_fill_time = g_pingpong_stats.fill_time_total_ms / g_pingpong_stats.fill_time_count; + PPP_DEBUG_WARN(" Buffer Fill Time (从首字节到flush):"); + PPP_DEBUG_WARN(" - Min: %u ms ⏱️", g_pingpong_stats.fill_time_min_ms); + PPP_DEBUG_WARN(" - Max: %u ms ⏱️", g_pingpong_stats.fill_time_max_ms); + PPP_DEBUG_WARN(" - Avg: %u ms ⏱️", avg_fill_time); + PPP_DEBUG_WARN(" - Sample Count: %u", g_pingpong_stats.fill_time_count); + } else { + PPP_DEBUG_WARN(" Buffer Fill Time: No data yet"); + } + + if (g_pingpong_stats.race_condition_detected > 0) { + PPP_DEBUG_ERROR(" Race Conditions Detected: %u 🔴", g_pingpong_stats.race_condition_detected); + } + PPP_DEBUG_WARN(""); + + // 中断层统计 + PPP_DEBUG_WARN("=== Interrupt Layer Statistics ==="); + PPP_DEBUG_WARN("Total UART Interrupts: %u", g_ppp_interrupt_stats.total_interrupts); + PPP_DEBUG_WARN(" - With RX Error: %u", g_ppp_interrupt_stats.error_count); + PPP_DEBUG_WARN(" · Parity Error: %u", g_uart_error_stats.parity_error_count); + PPP_DEBUG_WARN(" · Frame Error: %u", g_uart_error_stats.frame_error_count); + + // 如果有错误,显示错误率和详情 + if (g_ppp_interrupt_stats.total_interrupts > 0) { + uint32_t avg_bytes_per_irq = g_ppp_interrupt_stats.total_bytes_received / + g_ppp_interrupt_stats.total_interrupts; + PPP_DEBUG_WARN(" Average bytes per interrupt: %u", avg_bytes_per_irq); + + // 错误率 + if (g_ppp_interrupt_stats.error_count > 0) { + uint32_t error_rate = (g_ppp_interrupt_stats.error_count * 10000) / + g_ppp_interrupt_stats.total_interrupts; + PPP_DEBUG_WARN(" Error Rate: %u.%02u%%", error_rate / 100, error_rate % 100); + + // 如果错误率 > 1%,发出警告 + if (error_rate > 100) { + PPP_DEBUG_ERROR(" ⚠️ High error rate! Check UART config and cable."); + } + } + } + + // 如果有奇偶校验错误,打印最近一次的错误信息 + if (g_uart_error_stats.parity_error_count > 0) { + PPP_DEBUG_INFO(" Last Parity Error Info:"); + for (uint32_t i = 0; i < 4; i++) { + if (g_uart_error_stats.last_parity_error_info[i] != 0) { + PPP_DEBUG_INFO(" [%u]: 0x%08X", i, + g_uart_error_stats.last_parity_error_info[i]); + } + } + } + + // 如果有帧错误,打印最近一次的错误信息 + if (g_uart_error_stats.frame_error_count > 0) { + PPP_DEBUG_INFO(" Last Frame Error Info:"); + for (uint32_t i = 0; i < 4; i++) { + if (g_uart_error_stats.last_frame_error_info[i] != 0) { + PPP_DEBUG_INFO(" [%u]: 0x%08X", i, + g_uart_error_stats.last_frame_error_info[i]); + } + } + } + PPP_DEBUG_WARN(""); + + // 消息层统计 + PPP_DEBUG_WARN("=== Message Layer Statistics ==="); + PPP_DEBUG_WARN("Total Messages Sent to lwIP: %u", g_ppp_interrupt_stats.success_count); + PPP_DEBUG_WARN(" - lwIP Accept Error: %u", g_ppp_interrupt_stats.lwip_error_count); + if (g_ppp_interrupt_stats.success_count > 0) { + uint32_t avg_bytes = g_ppp_interrupt_stats.total_bytes_sent_to_lwip / + g_ppp_interrupt_stats.success_count; + PPP_DEBUG_WARN(" Average bytes per message: %u", avg_bytes); + + // 计算中断聚合比例 + if (g_ppp_interrupt_stats.total_interrupts > 0) { + uint32_t irq_per_msg = g_ppp_interrupt_stats.total_interrupts / + g_ppp_interrupt_stats.success_count; + PPP_DEBUG_WARN(" Interrupts per message: ~%u (aggregation ratio)", irq_per_msg); + } + } + PPP_DEBUG_WARN(""); + + // lwIP PPP帧解析层统计 + PPP_DEBUG_WARN("=== lwIP PPP Frame Parsing Statistics ==="); +#if LINK_STATS + PPP_DEBUG_WARN("Valid Frames Received: %u ✓", (uint32_t)lwip_stats.link.recv); + PPP_DEBUG_WARN("Frames Transmitted: %u", (uint32_t)lwip_stats.link.xmit); + + // 解析错误统计 + uint32_t total_parse_errors = (uint32_t)(lwip_stats.link.drop + + lwip_stats.link.chkerr + + lwip_stats.link.lenerr + + lwip_stats.link.memerr); + if (total_parse_errors > 0) { + PPP_DEBUG_WARN("Parse Errors: %u ⚠️", total_parse_errors); + PPP_DEBUG_WARN(" - FCS Checksum Error: %u", (uint32_t)lwip_stats.link.chkerr); + PPP_DEBUG_WARN(" - Length Error (incomplete): %u", (uint32_t)lwip_stats.link.lenerr); + PPP_DEBUG_WARN(" - Memory Error: %u", (uint32_t)lwip_stats.link.memerr); + PPP_DEBUG_WARN(" - Other Drop: %u", (uint32_t)lwip_stats.link.drop); + + // 计算错误率 + uint32_t total_frames = (uint32_t)(lwip_stats.link.recv + total_parse_errors); + if (total_frames > 0) { + uint32_t error_rate = (total_parse_errors * 10000) / total_frames; + PPP_DEBUG_WARN(" Frame Error Rate: %u.%02u%%", error_rate / 100, error_rate % 100); + + // FCS错误率(如果有) + if (lwip_stats.link.chkerr > 0) { + uint32_t fcs_rate = ((uint32_t)lwip_stats.link.chkerr * 10000) / total_frames; + PPP_DEBUG_WARN(" FCS Error Rate: %u.%02u%% (check UART/cable)", + fcs_rate / 100, fcs_rate % 100); + } + } + } else { + PPP_DEBUG_WARN("Parse Errors: 0 ✓ (no errors)"); + } +#else + PPP_DEBUG_WARN("LINK_STATS not enabled in lwIP"); +#endif + PPP_DEBUG_WARN(""); + + // 字节统计(与轮询模式格式一致) + uint32_t total_discarded = g_ppp_interrupt_stats.dropped_not_initialized + + g_ppp_interrupt_stats.dropped_lwip_error + + g_ppp_interrupt_stats.dropped_queue_full + + g_pingpong_stats.dropped_both_full; + + PPP_DEBUG_WARN("=== Byte-Level Statistics ==="); + PPP_DEBUG_WARN("Total Bytes Received (UART): %u", g_ppp_interrupt_stats.total_bytes_received); + PPP_DEBUG_WARN("Total Bytes Sent to lwIP: %u", g_ppp_interrupt_stats.total_bytes_sent_to_lwip); + + // 丢弃统计 + if (total_discarded > 0) { + PPP_DEBUG_WARN("Total Bytes Discarded: %u ⚠️", total_discarded); + PPP_DEBUG_WARN(" - Both Buffers Full: %u", g_pingpong_stats.dropped_both_full); + PPP_DEBUG_WARN(" - Queue Full: %u", g_ppp_interrupt_stats.dropped_queue_full); + PPP_DEBUG_WARN(" - Not Initialized: %u", g_ppp_interrupt_stats.dropped_not_initialized); + PPP_DEBUG_WARN(" - lwIP Input Error: %u", g_ppp_interrupt_stats.dropped_lwip_error); + } else { + PPP_DEBUG_WARN("Total Bytes Discarded: 0 ✓"); + } + PPP_DEBUG_WARN(""); + + // 计算字节级丢包率 + if (g_ppp_interrupt_stats.total_bytes_received > 0 && total_discarded > 0) { + uint32_t drop_rate = (total_discarded * 10000) / g_ppp_interrupt_stats.total_bytes_received; + PPP_DEBUG_WARN("Byte-Level Drop Rate: %u.%02u%%", drop_rate / 100, drop_rate % 100); + + // 如果丢包率超过1%,提示可能的原因 + if (drop_rate > 100) { + PPP_DEBUG_ERROR("⚠️ High byte drop rate detected!"); + if (g_pingpong_stats.dropped_both_full > 0) { + PPP_DEBUG_ERROR(" -> Buffers too small or processing too slow"); + } + if (g_ppp_interrupt_stats.dropped_queue_full > 0) { + PPP_DEBUG_ERROR(" -> Message queue too small or thread blocked"); + } + } + } + + PPP_DEBUG_WARN("================================================="); +} + +/** + * @brief 检查是否可以安全执行+++命令切换到命令模式 + * @return true 安全,false 不安全 + * + * 判断标准: + * 1. PPP已连接(仅PPP模式) + * 2. 接收空闲时间 >= 5秒 + * 3. 接收速率 < 100 bps + * 4. 所有buffer都是EMPTY状态 + * + * @note N706A_NET模式下始终返回true(纯AT模式无需切换) + */ +bool ppp_interrupt_is_safe_for_command_switch(void) +{ +#ifdef SUPPORT_N706A_NET + // N706A_NET模式:始终处于AT模式,无需切换,始终安全 + return true; +#else + // PPP模式:需要检查是否可以安全切换到AT命令模式 + // 1. 检查PPP是否已连接 + if (!ppp_lwip_is_connected()) { + PPP_DEBUG_DEBUG("[SAFE_CHECK] Not safe: PPP not connected"); + return false; + } + + uint32_t current_time = g_timer_timestamp_ms; + + // 2. 检查接收空闲时间 + if (g_ppp_data_state.last_rx_time_ms == 0) { + // 从未接收过数据,认为是空闲 + PPP_DEBUG_DEBUG("[SAFE_CHECK] No RX data yet, considered idle"); + } else { + uint32_t idle_ms = current_time - g_ppp_data_state.last_rx_time_ms; + if (idle_ms < PPP_SAFE_SWITCH_IDLE_MS) { + PPP_DEBUG_DEBUG("[SAFE_CHECK] Not safe: RX idle=%ums < threshold=%ums", + idle_ms, PPP_SAFE_SWITCH_IDLE_MS); + return false; + } + } + + // 3. 检查接收速率 + if (g_ppp_data_state.rx_rate_bps > PPP_LIGHT_THRESHOLD_BPS) { + PPP_DEBUG_DEBUG("[SAFE_CHECK] Not safe: RX rate=%u bps > threshold=%u bps", + g_ppp_data_state.rx_rate_bps, PPP_LIGHT_THRESHOLD_BPS); + return false; + } + + // 4. 检查所有buffer是否空闲 + if (g_ping_pong_buffers != NULL) { + uint32_t non_empty_count = 0; + for (uint8_t i = 0; i < PPP_PING_PONG_BUFFER_COUNT; i++) { + if (g_ping_pong_buffers[i].state != PPP_BUFFER_STATE_EMPTY) { + non_empty_count++; + PPP_DEBUG_DEBUG("[SAFE_CHECK] Buffer[%u] not empty (state=%d)", + i, g_ping_pong_buffers[i].state); + } + } + if (non_empty_count > 0) { + PPP_DEBUG_DEBUG("[SAFE_CHECK] Not safe: %u buffers not empty", non_empty_count); + return false; + } + } + + // 全部检查通过 + uint32_t idle_ms = (g_ppp_data_state.last_rx_time_ms > 0) ? + (current_time - g_ppp_data_state.last_rx_time_ms) : 0xFFFFFFFF; + PPP_DEBUG_INFO("[SAFE_CHECK] ✓ Safe for command switch (idle=%ums, rate=%u bps)", + idle_ms, g_ppp_data_state.rx_rate_bps); + return true; +#endif // SUPPORT_N706A_NET +} + +/** + * @brief 等待安全切换窗口 + * @param timeout_ms 超时时间(毫秒) + * @return true 找到安全窗口,false 超时 + */ +bool ppp_interrupt_wait_for_safe_switch(uint32_t timeout_ms) +{ + uint32_t start_time = ppp_get_current_time_ms(); + uint32_t elapsed = 0; + + PPP_DEBUG_INFO("[SAFE_WAIT] Waiting for safe switch window (timeout=%ums)...", timeout_ms); + + while (elapsed < timeout_ms) { + if (ppp_interrupt_is_safe_for_command_switch()) { + PPP_DEBUG_INFO("[SAFE_WAIT] ✓ Safe window found (waited %ums)", elapsed); + return true; + } + + // 短暂休眠,避免占用过多CPU + osal_msleep(100); + + elapsed = ppp_get_current_time_ms() - start_time; + } + + PPP_DEBUG_WARN("[SAFE_WAIT] Timeout after %ums, no safe window found", elapsed); + return false; +} + +/** + * @brief 打印数据传输状态信息(调试用) + */ +void ppp_interrupt_print_data_state(void) +{ + uint32_t current_time = g_timer_timestamp_ms; + uint32_t idle_ms = (g_ppp_data_state.last_rx_time_ms > 0) ? + (current_time - g_ppp_data_state.last_rx_time_ms) : 0xFFFFFFFF; + + PPP_DEBUG_WARN("╔═══════════════════════════════════════╗"); +#ifdef SUPPORT_N706A_NET + PPP_DEBUG_WARN("║ N706A_NET Data Transfer State ║"); +#else + PPP_DEBUG_WARN("║ PPP Data Transfer State ║"); +#endif + PPP_DEBUG_WARN("╠═══════════════════════════════════════╣"); +#ifndef SUPPORT_N706A_NET + PPP_DEBUG_WARN("║ PPP Connected: %s ║", + ppp_lwip_is_connected() ? "YES" : "NO "); +#endif + PPP_DEBUG_WARN("║ RX Idle Time: %u ms ║", idle_ms); + PPP_DEBUG_WARN("║ RX Rate: %u bytes/sec ║", g_ppp_data_state.rx_rate_bps); +#ifdef SUPPORT_PPP_SERVICE_INTERRUPT + PPP_DEBUG_WARN("║ CAT1 Vote: %s ║", + g_ppp_cat1_vote_added ? "ADDED (awake)" : "REMOVED (sleep)"); +#endif + PPP_DEBUG_WARN("║ Safe for +++: %s ║", + ppp_interrupt_is_safe_for_command_switch() ? "YES ✓" : "NO ✗"); + PPP_DEBUG_WARN("╠═══════════════════════════════════════╣"); + + // Buffer状态 + if (g_ping_pong_buffers != NULL) { + PPP_DEBUG_WARN("║ Buffer Status: ║"); + for (uint8_t i = 0; i < PPP_PING_PONG_BUFFER_COUNT; i++) { + const char *state_str = "?"; + switch (g_ping_pong_buffers[i].state) { + case PPP_BUFFER_STATE_EMPTY: state_str = "EMPTY "; break; + case PPP_BUFFER_STATE_FILLING: state_str = "FILLING "; break; + case PPP_BUFFER_STATE_FULL: state_str = "FULL "; break; + case PPP_BUFFER_STATE_PROCESSING: state_str = "PROCESSING"; break; + } + PPP_DEBUG_WARN("║ Buffer[%u]: %s ║", i, state_str); + } + } else { + PPP_DEBUG_WARN("║ Buffers: Not allocated ║"); + } + + PPP_DEBUG_WARN("╚═══════════════════════════════════════╝"); +} + +/** + * @brief 暂停PPP中断处理(用于临时切换到AT模式) + * @return PPP_ERR_OK 成功,其他值失败 + * + * 极简设计(只需停止 UART 中断): + * + * 关键机制: + * 1. UART 中断停止 → 无新数据写入 buffer + * 2. Timer 继续运行 → 自动 flush buffer 中的残留数据 + * 3. 线程继续运行 → 自动处理完队列后阻塞(使用 OSAL_MSGQ_WAIT_FOREVER) + * + * 不需要: + * - ✗ 不停止 timer(让它自动 flush) + * - ✗ 不手动 flush buffer(timer 会处理) + * - ✗ 不等待队列排空(线程会自动阻塞) + * - ✗ 不暂停线程(阻塞队列读取是天然的暂停) + * + * 资源保留: + * - ✓ 消息队列 + * - ✓ 乒乓缓冲区 + * - ✓ 统计信息 + * - ✓ 定时器(继续运行) + * - ✓ 线程(自动阻塞) + */ +ppp_error_t ppp_interrupt_pause(void) +{ + if (!g_ppp_interrupt_enabled) { + PPP_DEBUG_WARN("PPP interrupt not enabled, nothing to pause"); + return PPP_ERR_OK; + } + + PPP_DEBUG_CRITICAL("=== PPP Interrupt Pause (Minimal) ==="); + + // ========== 唯一操作: 注销UART中断回调 ========== + uint8_t uart_bus = ppp_serial_get_uart_bus(); + uapi_uart_unregister_rx_callback(uart_bus); + PPP_DEBUG_INFO("UART RX callback unregistered"); + + // ========== 自动处理机制(无需手动操作)========== + // ✓ Timer 继续运行(g_ppp_timer_running 保持 true) + // → 定期检查 buffer(30ms 周期) + // → 发现有数据就自动 flush 到队列 + // → 最终所有 buffer 都会被 flush 干净 + // → buffer 按 first_write_time 排序 flush,保证顺序正确 + // + // ✓ 线程继续运行(使用 OSAL_MSGQ_WAIT_FOREVER) + // → 从队列读取并处理所有消息 + // → 队列为空时自然阻塞在 osal_msg_queue_read_copy() + // → 无需 suspend 线程,阻塞读取就是天然的暂停 + // + // ✓ 资源完全保留 + // → 消息队列保持 + // → 乒乓缓冲区保持 + // → 统计信息保持 + // → 定时器保持运行 + // → 线程保持(阻塞状态) + + PPP_DEBUG_INFO("Timer auto-flushes residual data, thread auto-blocks on empty queue"); + PPP_DEBUG_CRITICAL("=== PPP Interrupt Paused ==="); + PPP_DEBUG_INFO("All resources preserved, ready for quick resume"); + + return PPP_ERR_OK; +} + +/** + * @brief 恢复PPP中断处理(从AT模式返回) + * @return PPP_ERR_OK 成功,其他值失败 + * + * 极简设计(只需重新注册 UART 中断): + * + * 关键机制: + * 1. Timer 一直在运行 → 已自动 flush 所有 buffer + * 2. 线程自动唤醒 → 当新数据到达队列时自动从阻塞中返回 + * 3. Buffer 已经干净 → Timer 已经 flush 过了 + * + * 不需要: + * - ✗ 不验证资源(pause 时保留了所有资源) + * - ✗ 不清理 buffer(timer 已经 flush 过了) + * - ✗ 不重启 timer(一直在运行) + * - ✗ 不恢复线程(会自动唤醒) + */ +ppp_error_t ppp_interrupt_resume(void) +{ + if (!g_ppp_interrupt_enabled) { + PPP_DEBUG_ERROR("PPP interrupt not enabled, cannot resume"); + return PPP_ERR_INVALID_STATE; + } + + PPP_DEBUG_CRITICAL("=== PPP Interrupt Resume (Minimal) ==="); + + // ========== 唯一操作: 重新注册UART中断回调 ========== + uint8_t uart_bus = ppp_serial_get_uart_bus(); + + errcode_t ret = uapi_uart_register_rx_callback( + uart_bus, + UART_RX_CONDITION_FULL_OR_SUFFICIENT_DATA_OR_IDLE, + 8, // 阈值:8 字节 + ppp_uart_rx_interrupt_handler + ); + + if (ret != ERRCODE_SUCC) { + PPP_DEBUG_ERROR("Failed to register UART RX callback: 0x%x", ret); + return PPP_ERR_UART; + } + + PPP_DEBUG_INFO("UART RX callback registered"); + + // ========== 自动唤醒机制(无需手动操作)========== + // ✓ Timer 一直在运行 + // → 已经自动 flush 了所有残留数据 + // → Buffer 已经是干净的 + // + // ✓ 线程自动唤醒 + // → 当新数据到达时,UART 中断写入队列 + // → 线程自动从 osal_msg_queue_read_copy() 返回 + // → 继续处理新数据 + // + // ✓ 状态自然恢复 + // → 无需手动清理或验证 + // → 系统自动进入正常工作状态 + + PPP_DEBUG_INFO("Timer kept running, thread auto-wakes on new data"); + PPP_DEBUG_CRITICAL("=== PPP Interrupt Resumed ==="); + PPP_DEBUG_INFO("Ready to receive PPP data"); + + return PPP_ERR_OK; +} \ No newline at end of file diff --git a/code/sdk/zhihui/n706a_net/src/ppp_power_ctl.c b/code/sdk/zhihui/n706a_net/src/ppp_power_ctl.c new file mode 100644 index 000000000..67169b612 --- /dev/null +++ b/code/sdk/zhihui/n706a_net/src/ppp_power_ctl.c @@ -0,0 +1,495 @@ +/* + * @brief PPP模块电源控制实现 + * @description 提供PPP模块专用的4G电源控制功能,与SLIP模块互斥使用 + * Copyright (c) CompanyNameMagicTag 2024-2024. All rights reserved. + */ + +#include "ppp_power_ctl.h" +#include "ppp_debug.h" +#include "gpio.h" +#include "gpio_porting.h" +#include "platform_core.h" +#include "pinctrl_porting.h" +#include "osal_debug.h" +#include "securec.h" +#include "tcxo.h" // 用于获取系统时间(去抖用) + +// PPP模块的4G控制引脚配置 +static ppp_ctl_pin_4g_t g_ppp_cat1_ctl_pins = { + #if defined(BOARD_FD230) + PIN_NONE, //bat_pin + 1, + S_AGPIO_L23, // pwr_pin + 1, + S_AGPIO_L28,//reset_pin + 0, + S_AGPIO_L31,//ring_pin + 1, + S_AGPIO_L30,//wakemodem_pin + 1, + #elif defined(BOARD_L22) + S_AGPIO_L14, //bat_pin + 1, + S_AGPIO_L23, // pwr_pin + 1, + PIN_NONE,//reset_pin + 0, + S_AGPIO_L31,//ring_pin + 1, + S_AGPIO_L30,//wakemodem_pin + 1, + #elif defined(BOARD_W9) || defined(BOARD_N9) + S_AGPIO_L17, //bat_pin + 1, + S_AGPIO_L23, // pwr_pin + 1, + S_AGPIO_L28,//reset_pin + 0, + S_AGPIO_L31,//ring_pin + 1, + S_AGPIO_L30,//wakemodem_pin + 1, + #elif defined(BOARD_LEINIAO) + S_AGPIO_L18, //bat_pin + 1, + S_AGPIO_L23, // pwr_pin + 1, + S_AGPIO_L24,//reset_pin + 1, //电路中 控制gpio拉高,reset才拉低 + S_MGPIO21,//ring_pin GPIO_16 + 2, + S_MGPIO22,//wakemodem_pin GPIO_17 Main_DTR + 1, + #else + PIN_NONE, //bat_pin + 1, + S_AGPIO_L23, // pwr_pin + 1, + S_AGPIO_L28,//reset_pin + 0, + S_AGPIO_L31,//ring_pin + 1, + S_AGPIO_L30,//wakemodem_pin + 1, + #endif +}; + +// PPP模块的4G电源状态 +static bool g_ppp_cat1_power_status = false; + +/** + * @brief PPP模块GPIO初始化 + */ +void ppp_api_gpio_init(void) +{ + PPP_DEBUG_PRINT("PPP GPIO init\n"); + + pin_t gpiopin=g_ppp_cat1_ctl_pins.bat_pin; + if (gpiopin!=PIN_NONE) + { + uapi_pin_set_mode(gpiopin, (pin_mode_t)HAL_PIO_FUNC_GPIO); + uapi_gpio_set_dir(gpiopin,GPIO_DIRECTION_OUTPUT); + } + gpiopin=g_ppp_cat1_ctl_pins.pwr_pin; + if (gpiopin!=PIN_NONE) + { + uapi_pin_set_mode(gpiopin, (pin_mode_t)HAL_PIO_FUNC_GPIO); + uapi_gpio_set_dir(gpiopin,GPIO_DIRECTION_OUTPUT); + } + gpiopin=g_ppp_cat1_ctl_pins.reset_pin; + if (gpiopin!=PIN_NONE) + { + uapi_pin_set_mode(gpiopin, (pin_mode_t)HAL_PIO_FUNC_GPIO); + uapi_gpio_set_dir(gpiopin,GPIO_DIRECTION_OUTPUT); + } + gpiopin=g_ppp_cat1_ctl_pins.wakemodem_pin; + if (gpiopin!=PIN_NONE) + { + uapi_pin_set_mode(gpiopin, (pin_mode_t)HAL_PIO_FUNC_GPIO); + uapi_gpio_set_dir(gpiopin,GPIO_DIRECTION_OUTPUT); + } + + gpiopin=g_ppp_cat1_ctl_pins.ring_pin; + if (gpiopin != PIN_NONE) + { + uapi_pin_set_mode(gpiopin, (pin_mode_t)HAL_PIO_FUNC_GPIO); + uapi_gpio_set_dir(gpiopin, GPIO_DIRECTION_INPUT); + switch (g_ppp_cat1_ctl_pins.ring_pin_en_value) + { + case 0: + uapi_pin_set_pull(gpiopin, (pin_pull_t)HAL_PIO_PULL_UP); + uapi_gpio_register_isr_func(gpiopin, GPIO_INTERRUPT_FALLING_EDGE, ppp_ring_isr_callback); + PPP_DEBUG_PRINT("PPP: Register ring pin %d falling edge interrupt\n", gpiopin); + break; + case 1: + uapi_pin_set_pull(gpiopin, (pin_pull_t)HAL_PIO_PULL_DOWN); + uapi_gpio_register_isr_func(gpiopin, GPIO_INTERRUPT_RISING_EDGE, ppp_ring_isr_callback); + PPP_DEBUG_PRINT("PPP: Register ring pin %d rising edge interrupt\n", gpiopin); + break; + case 2: + default: + uapi_pin_set_pull(gpiopin, (pin_pull_t)HAL_PIO_PULL_MAX); + uapi_gpio_register_isr_func(gpiopin, GPIO_INTERRUPT_DEDGE, ppp_ring_isr_callback); + PPP_DEBUG_PRINT("PPP: Register ring pin %d both edge interrupt\n", gpiopin); + break; + } + } +} + +void ppp_api_leiniao_serial_pin_to_gpio_input(void) +{ + uapi_pin_set_mode(SIO_UART_TX_PIN, (pin_mode_t)HAL_PIO_FUNC_GPIO); + uapi_gpio_set_dir(SIO_UART_TX_PIN, GPIO_DIRECTION_INPUT); + uapi_pin_set_mode(SIO_UART_RX_PIN, (pin_mode_t)HAL_PIO_FUNC_GPIO); + uapi_gpio_set_dir(SIO_UART_RX_PIN, GPIO_DIRECTION_INPUT); + PPP_DEBUG_PRINT("PPP: Leiniao serial pin to gpio input\n"); +} + +/** + * @brief PPP模块设置电源引脚状态 + */ +uint32_t ppp_api_cat1_power_pin(uint32_t pwron) +{ + pin_t en_pin = g_ppp_cat1_ctl_pins.pwr_pin; + uint8_t en_value = g_ppp_cat1_ctl_pins.pwr_pin_en_value; + + if (en_pin == PIN_NONE) { + PPP_DEBUG_PRINT("PPP: cat1_power_on en_pin is none\n"); + return 0xff; + } + + if (pwron == 0) { + uapi_gpio_set_dir(en_pin, GPIO_DIRECTION_OUTPUT); + if (en_value) { + uapi_gpio_set_val(en_pin, GPIO_LEVEL_LOW); + PPP_DEBUG_PRINT("PPP: Power Cat1 Off! pin %d value low\n", en_pin); + } else { + uapi_gpio_set_val(en_pin, GPIO_LEVEL_HIGH); + PPP_DEBUG_PRINT("PPP: Power Cat1 Off! pin %d value high\n", en_pin); + } + } else { + uapi_gpio_set_dir(en_pin, GPIO_DIRECTION_OUTPUT); + if (en_value) { + uapi_gpio_set_val(en_pin, GPIO_LEVEL_HIGH); + PPP_DEBUG_PRINT("PPP: Power Cat1 On! pin %d value high\n", en_pin); + } else { + uapi_gpio_set_val(en_pin, GPIO_LEVEL_LOW); + PPP_DEBUG_PRINT("PPP: Power Cat1 On! pin %d value low\n", en_pin); + } + } + return 0; +} + +/** + * @brief PPP模块设置复位引脚状态 + */ +uint32_t ppp_api_cat1_reset_pin(uint32_t pwron) +{ + pin_t en_pin = g_ppp_cat1_ctl_pins.reset_pin; + uint8_t en_value = g_ppp_cat1_ctl_pins.reset_pin_en_value; + + if (en_pin == PIN_NONE) { + PPP_DEBUG_PRINT("PPP: cat1_reset en_pin is none\n"); + return 0xff; + } + + if (pwron == 0) { //取消复位 + uapi_gpio_set_dir(en_pin, GPIO_DIRECTION_OUTPUT); + if (en_value) { //拉高复位,取消复位,应拉低 + uapi_gpio_set_val(en_pin, GPIO_LEVEL_LOW); + PPP_DEBUG_PRINT("PPP: Cat1 reset Off! pin %d value low\n", en_pin); + } else { //拉低复位,取消复位,应拉高 + uapi_gpio_set_val(en_pin, GPIO_LEVEL_HIGH); + PPP_DEBUG_PRINT("PPP: Cat1 reset Off! pin %d value high\n", en_pin); + } + } else { //复位 + uapi_gpio_set_dir(en_pin, GPIO_DIRECTION_OUTPUT); + if (en_value) { //拉高复位 + uapi_gpio_set_val(en_pin, GPIO_LEVEL_HIGH); + PPP_DEBUG_PRINT("PPP: Cat1 reset On! pin %d value high\n", en_pin); + } else { //拉低复位 + uapi_gpio_set_val(en_pin, GPIO_LEVEL_LOW); + PPP_DEBUG_PRINT("PPP: Cat1 reset On! pin %d value low\n", en_pin); + } + } + uint8_t value = uapi_gpio_get_output_val(en_pin); + PPP_DEBUG_PRINT("PPP: Cat1 reset value %d\n", value); + return 0; +} + +/** + * @brief PPP模块设置电池引脚状态 + */ +uint32_t ppp_api_cat1_bat_pin(uint32_t pwron) +{ + pin_t en_pin = g_ppp_cat1_ctl_pins.bat_pin; + uint8_t en_value = g_ppp_cat1_ctl_pins.bat_pin_en_value; + + if (en_pin == PIN_NONE) { + PPP_DEBUG_PRINT("PPP: cat1_bat_on en_pin is none\n"); + return 0xff; + } + + if (pwron == 0) { + uapi_gpio_set_dir(en_pin, GPIO_DIRECTION_OUTPUT); + if (en_value) { + uapi_gpio_set_val(en_pin, GPIO_LEVEL_LOW); + PPP_DEBUG_PRINT("PPP: Cat1 bat Off! pin %d value low\n", en_pin); + } else { + uapi_gpio_set_val(en_pin, GPIO_LEVEL_HIGH); + PPP_DEBUG_PRINT("PPP: Cat1 bat Off! pin %d value high\n", en_pin); + } + } else { + uapi_gpio_set_dir(en_pin, GPIO_DIRECTION_OUTPUT); + if (en_value) { + uapi_gpio_set_val(en_pin, GPIO_LEVEL_HIGH); + PPP_DEBUG_PRINT("PPP: Cat1 bat On! pin %d value high\n", en_pin); + } else { + uapi_gpio_set_val(en_pin, GPIO_LEVEL_LOW); + PPP_DEBUG_PRINT("PPP: Cat1 bat On! pin %d value low\n", en_pin); + } + } + return 0; +} + +/** + * @brief PPP模块检查4G是否使能 + */ +bool ppp_api_is_cat1_enabled(void) +{ +#if defined(BOARD_W9) || defined(BOARD_N9) || defined(BOARD_LEINIAO) + pin_t en_pin = g_ppp_cat1_ctl_pins.bat_pin; + uint8_t en_value = g_ppp_cat1_ctl_pins.bat_pin_en_value; +#else + pin_t en_pin = g_ppp_cat1_ctl_pins.pwr_pin; + uint8_t en_value = g_ppp_cat1_ctl_pins.pwr_pin_en_value; +#endif + + if (en_pin == PIN_NONE) { + PPP_DEBUG_PRINT("PPP: is_cat1_enabled en_pin is none\n"); + return false; + } + + if (en_value == uapi_gpio_get_output_val(en_pin)) { + PPP_DEBUG_PRINT("PPP: is_cat1_enabled en_value is %d\n", en_value); + return true; + } else { + PPP_DEBUG_PRINT("PPP: is_cat1_enabled en_value is %d\n", en_value); + return false; + } +} + +/** + * @brief PPP模块4G上电序列 + */ +void ppp_power_on_cat1(void) +{ +#if defined(BOARD_FD230) + ppp_api_cat1_power_pin(1); + ppp_api_cat1_reset_pin(0); +#elif (defined(BOARD_W9) || defined(BOARD_N9) ) + ppp_api_cat1_power_pin(1); + ppp_api_cat1_bat_pin(1); + osal_msleep(900); // 使用OSAL延时函数 + ppp_api_cat1_power_pin(0); +#elif defined(BOARD_LEINIAO) + ppp_api_cat1_reset_pin(0); + ppp_api_cat1_power_pin(1); + ppp_api_cat1_bat_pin(1); + osal_msleep(900); // 使用OSAL延时函数 + ppp_api_cat1_power_pin(0); +#if defined(PPP_DO_SERIAL_TEST) && (PPP_DO_SERIAL_TEST == 1) + ppp_api_cat1_reset_pin(1); +#endif + +#endif + + g_ppp_cat1_power_status = true; + PPP_DEBUG_PRINT("PPP: Cat1 power on sequence completed\n"); +} + +/** + * @brief PPP模块4G下电序列 + */ +void ppp_power_off_cat1(void) +{ +#if defined(BOARD_FD230) + ppp_api_cat1_reset_pin(1); + ppp_api_cat1_power_pin(0); +#elif (defined(BOARD_W9) || defined(BOARD_N9) ) + ppp_api_cat1_power_pin(0); + ppp_api_cat1_bat_pin(0); +#elif defined(BOARD_LEINIAO) + ppp_api_cat1_reset_pin(0); + ppp_api_cat1_power_pin(0); + ppp_api_cat1_bat_pin(0); +#endif + + g_ppp_cat1_power_status = false; + PPP_DEBUG_PRINT("PPP: Cat1 power off sequence completed\n"); +} + + +void ppp_power_reset_cat1(void) +{ + ppp_api_cat1_reset_pin(1); + osal_msleep(50); + ppp_api_cat1_reset_pin(0); +} + +/** + * @brief PPP模块唤醒Cat1 + */ +void ppp_api_wakeup_cat1(uint8_t wakeup) +{ + pin_t wake_pin = g_ppp_cat1_ctl_pins.wakemodem_pin; + uint8_t wake_value = g_ppp_cat1_ctl_pins.wakemodem_pin_en_value; + + if (wake_pin == PIN_NONE) { + PPP_DEBUG_PRINT("PPP: wakeup_cat1 wake_pin is none\n"); + return; + } + + uapi_gpio_set_dir(wake_pin, GPIO_DIRECTION_OUTPUT); + if (wakeup) { + if (wake_value) { + uapi_gpio_set_val(wake_pin, GPIO_LEVEL_HIGH); + } else { + uapi_gpio_set_val(wake_pin, GPIO_LEVEL_LOW); + } + PPP_DEBUG_PRINT("PPP: Wakeup Cat1 pin %d\n", wake_pin); + } else { + if (wake_value) { + uapi_gpio_set_val(wake_pin, GPIO_LEVEL_LOW); + } else { + uapi_gpio_set_val(wake_pin, GPIO_LEVEL_HIGH); + } + PPP_DEBUG_PRINT("PPP: Release Wakeup Cat1 pin %d\n", wake_pin); + } +} + +/** + * @brief PPP模块保持唤醒Cat1 + */ +void ppp_api_keep_wakeup(uint8_t keep_wakeup) +{ + // 保持唤醒逻辑实现 + PPP_DEBUG_PRINT("PPP: Keep wakeup Cat1: %d\n", keep_wakeup); +} + +/** + * @brief PPP模块注册4G控制引脚配置 + */ +errcode_t ppp_register_ctl_pin(ppp_ctl_pin_4g_t *ctl_pin) +{ + if (ctl_pin == NULL) { + PPP_DEBUG_PRINT("PPP: register_ctl_pin invalid parameter\n"); + return ERRCODE_INVALID_PARAM; + } + + (void)memcpy_s(&g_ppp_cat1_ctl_pins, sizeof(ppp_ctl_pin_4g_t), + ctl_pin, sizeof(ppp_ctl_pin_4g_t)); + + PPP_DEBUG_PRINT("PPP: Control pins registered successfully\n"); + return ERRCODE_SUCC; +} + +/** + * @brief PPP模块Cat1唤醒中断回调函数(带去抖处理) + */ +void ppp_ring_isr_callback(pin_t pin, uintptr_t param) +{ + (void)param; + + // ⚠️ 去抖处理:防止信号抖动导致频繁中断 + static uint64_t last_interrupt_time = 0; + static gpio_level_t last_level = GPIO_LEVEL_LOW; + static uint32_t same_level_count = 0; + + uint64_t current_time = uapi_tcxo_get_ms(); // 获取当前时间(毫秒) + gpio_level_t val = uapi_gpio_get_val(pin); + + // 去抖时间窗口:50ms(可根据实际情况调整) + #define RING_DEBOUNCE_TIME_MS 50 + + // 检查是否在去抖时间内 + if ((current_time - last_interrupt_time) < RING_DEBOUNCE_TIME_MS) { + // 在去抖时间内,检查电平是否一致 + if (val == last_level) { + same_level_count++; + // 如果同一电平连续触发超过5次,打印警告 + if (same_level_count == 5) { + PPP_DEBUG_WARN("PPP: ⚠️ Ring pin %d signal bouncing (level=%d, count=%u)\n", + pin, val, same_level_count); + } + } else { + same_level_count = 1; + last_level = val; + } + + // 清除中断标志,但不处理 + // uapi_gpio_clear_interrupt(pin); + // return; + } + + // 去抖时间已过,处理中断 + last_interrupt_time = current_time; + last_level = val; + same_level_count = 1; + + if (val == GPIO_LEVEL_HIGH) { + PPP_DEBUG_WARN("PPP: Cat1 ring interrupt on pin %d → HIGH \n", pin); + cat1_api_add_cat1_vote(); +} else { + PPP_DEBUG_WARN("PPP: Cat1 ring interrupt on pin %d → LOW\n", pin); + //idle后 remove + } + uapi_gpio_clear_interrupt(pin); + +} + +/** + * @brief PPP模块专用的4G使能控制接口 + */ +void ppp_api_enable_cat1(bool isopen) +{ + PPP_DEBUG_PRINT("PPP: Enable Cat1: %s\n", isopen ? "true" : "false"); + + if (isopen) { + if (g_ppp_cat1_power_status) { + PPP_DEBUG_PRINT("PPP: Cat1 already powered on\n"); + return; + } + + // PPP模块开启4G + ppp_power_on_cat1(); + + // 可以在这里添加PPP特定的初始化逻辑 + // 例如:启动PPP协议栈等 + + } else { + if (!g_ppp_cat1_power_status) { + PPP_DEBUG_PRINT("PPP: Cat1 already powered off\n"); + return; + } + + // PPP模块关闭4G + ppp_power_off_cat1(); + + // 可以在这里添加PPP特定的清理逻辑 + // 例如:停止PPP协议栈等 + } +} + +/** + * @brief PPP模块反初始化 + */ +void ppp_api_deinit(void) +{ + PPP_DEBUG_PRINT("PPP: deinit\n"); + + // 关闭电源 + ppp_api_cat1_power_pin(0); + + g_ppp_cat1_power_status = false; +} \ No newline at end of file diff --git a/code/sdk/zhihui/n706a_net/src/ppp_serial.c b/code/sdk/zhihui/n706a_net/src/ppp_serial.c new file mode 100644 index 000000000..4d3049854 --- /dev/null +++ b/code/sdk/zhihui/n706a_net/src/ppp_serial.c @@ -0,0 +1,971 @@ +#include "ppp_serial.h" +#include "ppp_config.h" +#include "ppp_types.h" +#include "ppp_debug.h" +#include "uart.h" // 简化路径 +#include "schedule/osal_task.h" +#include "semaphore/osal_semaphore.h" +#include "lock/osal_mutex.h" +#include "memory/osal_addr.h" +#include "soc_osal.h" +#include "securec.h" +#include +#include "arch_port.h" +#include "pmu_interrupt.h" // PMU中断管理:UART唤醒配置 +#include "pm_definition.h" + +/* PPP性能优化配置 */ +#define PPP_OPTIMIZED_UART_WRITE_TIMEOUT 200 /* UART写超时优化: 1000ms -> 200ms */ +#define PPP_OPTIMIZED_UART_READ_TIMEOUT 100 /* UART读超时优化: 200ms -> 100ms */ +#if defined(CONFIG_UART_SUPPORT_DMA) +/* + +uint8_t src_width; 源端传输数据宽度 - 0: 1字节 + * - 1: 2字节 + * - 2: 4字节 +uint8_t dest_width; 目的端传输数据宽度 - 0: 1字节 + * - 1: 2字节 + * - 2: 4字节 +uint8_t burst_length; - 0: burst长度是1 + * - 1: burst长度是4 + * - 2: burst长度是8 + * - 3: burst长度是16 +uint8_t priority; 传输通道优先级(最小为0以及最大为3) +*/ +#define UART_DMA_WIDTH 2 +#define UART_DMA_BURST_LENGTH 3 +#define UART_DMA_PRIORITY 2 +#define UART_DMA_TX_ENABLE 0 +#define UART_DMA_RX_ENABLE 1 +#endif +/* 串口上下文结构 - 优化缓存机制 */ +typedef struct { + bool initialized; + ppp_serial_mode_t current_mode; + uint8_t uart_bus; + uint8_t irq_num; + + /* UART配置参数 */ + uart_pin_config_t pin_config; + uart_attr_t uart_attr; + uart_buffer_config_t buffer_config; + uart_extra_attr_t extra_attr; + + /* 统一的接收缓冲区(AT和PPP模式共用) */ + uint8_t *rx_buffer; + uint32_t rx_buffer_size; + uint32_t rx_head; + uint32_t rx_tail; + uint32_t rx_count; + + /* 同步对象 */ + osal_mutex rx_mutex; // 改为结构体而不是指针 + osal_semaphore rx_semaphore; // 改为结构体而不是指针 + + /* 回调函数 */ + ppp_serial_rx_callback_t rx_callback; + void *rx_callback_arg; + + /* 统计信息 */ + uint32_t tx_bytes; + uint32_t rx_bytes; + uint32_t tx_errors; + uint32_t rx_errors; + + /* 智能flush策略相关 */ + uint32_t last_tx_time; // 最后发送时间(ms) + uint32_t consecutive_small_packets; // 连续小包计数 + uint32_t idle_flush_threshold; // 空闲flush阈值(ms) +} ppp_serial_context_t; + +static ppp_serial_context_t g_serial_ctx = {0}; + +/* 内部函数声明 */ +static void ppp_serial_rx_callback(const void *buffer, uint16_t length, bool remaining); +static uint32_t ppp_serial_buffer_available_space(void); +static uint32_t ppp_serial_buffer_data_count(void); +/* ppp_serial_is_ppp_control_packet() 已删除 - N706A_NET不需要PPP协议检测 */ +static uint32_t ppp_serial_get_current_time_ms(void); +static ppp_error_t ppp_serial_register_irq(void); +static ppp_error_t ppp_serial_unregister_irq(void); + +/* IRQ相关代码已移除 - 使用简化的直接UART读取方式 */ + +/** + * @brief 初始化PPP串口 - 统一缓冲区管理 + */ +ppp_error_t ppp_serial_init(const ppp_serial_config_t *config) +{ + if (!config || g_serial_ctx.initialized) { + return PPP_ERR_INVALID_PARAM; + } + + PPP_DEBUG_INFO("=== PPP Serial Initialization Start ==="); + PPP_DEBUG_DEBUG("Initializing PPP serial with unified buffer management"); + + /* 配置参数 - 降级为DEBUG级别 */ + PPP_DEBUG_DEBUG("Configuration parameters:"); + PPP_DEBUG_INFO(" - UART Bus: %u", config->uart_bus); + PPP_DEBUG_DEBUG(" - IRQ Num: %u", config->irq_num); + PPP_DEBUG_INFO(" - Baudrate: %u", config->baudrate); + PPP_DEBUG_DEBUG(" - TX Pin: %u", config->tx_pin); + PPP_DEBUG_DEBUG(" - RX Pin: %u", config->rx_pin); + PPP_DEBUG_DEBUG(" - RX Buffer Size: %u bytes", config->rx_buffer_size); + PPP_DEBUG_DEBUG(" - RX Callback: %s", config->rx_callback ? "Set" : "NULL"); + + /* 配置UART参数 */ + g_serial_ctx.uart_bus = config->uart_bus; + g_serial_ctx.irq_num = config->irq_num; + g_serial_ctx.pin_config.tx_pin = config->tx_pin; + g_serial_ctx.pin_config.rx_pin = config->rx_pin; + g_serial_ctx.uart_attr.baud_rate = config->baudrate; + g_serial_ctx.uart_attr.data_bits = UART_DATA_BIT_8; + g_serial_ctx.uart_attr.stop_bits = UART_STOP_BIT_1; + g_serial_ctx.uart_attr.parity = UART_PARITY_NONE; + + PPP_DEBUG_DEBUG("UART attributes configured:"); + PPP_DEBUG_DEBUG(" - Baud Rate: %u", g_serial_ctx.uart_attr.baud_rate); + PPP_DEBUG_DEBUG(" - Data Bits: %d", g_serial_ctx.uart_attr.data_bits); + PPP_DEBUG_DEBUG(" - Stop Bits: %d", g_serial_ctx.uart_attr.stop_bits); + PPP_DEBUG_DEBUG(" - Parity: %d", g_serial_ctx.uart_attr.parity); + + /* 动态分配统一的接收缓冲区 */ + g_serial_ctx.rx_buffer_size = config->rx_buffer_size; + g_serial_ctx.rx_buffer = (uint8_t *)osal_kmalloc(g_serial_ctx.rx_buffer_size, OSAL_GFP_KERNEL); + if (g_serial_ctx.rx_buffer == NULL) { + PPP_DEBUG_ERROR("Failed to allocate RX buffer (%u bytes)", g_serial_ctx.rx_buffer_size); + return PPP_ERR_MEMORY; + } + PPP_DEBUG_DEBUG("RX buffer allocated: %u bytes at 0x%p", g_serial_ctx.rx_buffer_size, g_serial_ctx.rx_buffer); + + /* 配置UART使用我们分配的接收缓冲区 */ + g_serial_ctx.buffer_config.rx_buffer = g_serial_ctx.rx_buffer; + g_serial_ctx.buffer_config.rx_buffer_size = g_serial_ctx.rx_buffer_size; + + PPP_DEBUG_DEBUG("UART buffer config:"); + PPP_DEBUG_DEBUG(" - RX Buffer Address: 0x%p", g_serial_ctx.buffer_config.rx_buffer); + PPP_DEBUG_DEBUG(" - RX Buffer Size: %u", g_serial_ctx.buffer_config.rx_buffer_size); + + // errcode_t rett = uapi_uart_deinit(g_serial_ctx.uart_bus); + // if (rett != ERRCODE_SUCC) { + // PPP_DEBUG_ERROR("Failed to deinitialize UART: error code = %d (0x%x)", rett, rett); + // osal_kfree(g_serial_ctx.rx_buffer); + // g_serial_ctx.rx_buffer = NULL; + // return PPP_ERR_UART; + // } + // PPP_DEBUG_INFO("UART deinitialized successfully"); + + /* 初始化UART */ + PPP_DEBUG_DEBUG("Calling uapi_uart_init..."); + + // ⚠️ 修复:区分轮询模式和中断模式的UART初始化策略 + // - 轮询模式(SUPPORT_PPP_SERVICE_POLLING):uart_porting.c中UART_BUS_0的pin=PIN_NONE, + // 未在启动时初始化,需要在这里完整初始化UART(但DMA已在chip_init.c中初始化) + // - 中断模式(SUPPORT_PPP_SERVICE_INTERRUPT):uart_porting.c中已完整初始化UART, + // 这里只需要设置波特率 + + #if defined(SUPPORT_PPP_SERVICE_POLLING) + // 轮询模式:完整初始化UART(注意:DMA已在chip_init.c:152-153初始化,不要重复调用) + PPP_DEBUG_WARN("Polling mode: Initializing UART for the first time"); + #if defined(CONFIG_UART_SUPPORT_DMA) + g_serial_ctx.extra_attr.tx_dma_enable = UART_DMA_TX_ENABLE; + g_serial_ctx.extra_attr.tx_int_threshold = 0; + g_serial_ctx.extra_attr.rx_dma_enable = UART_DMA_RX_ENABLE; + g_serial_ctx.extra_attr.rx_int_threshold = 0; + PPP_DEBUG_WARN("Polling mode with DMA: TX DMA and RX DMA both enabled"); + errcode_t ret = uapi_uart_init(g_serial_ctx.uart_bus, + &g_serial_ctx.pin_config, + &g_serial_ctx.uart_attr, + &g_serial_ctx.extra_attr, + &g_serial_ctx.buffer_config); + #else + errcode_t ret = uapi_uart_init(g_serial_ctx.uart_bus, + &g_serial_ctx.pin_config, + &g_serial_ctx.uart_attr, + NULL, + &g_serial_ctx.buffer_config); + #endif + #elif defined(SUPPORT_PPP_SERVICE_INTERRUPT) || defined(SUPPORT_N706A_NET) + // 中断模式/N706A_NET模式:UART已在uart_porting.c:uart_config_init()中初始化,只设置波特率 + PPP_DEBUG_WARN("Interrupt/N706A_NET mode: UART already initialized, updating baudrate"); + errcode_t ret = uapi_uart_set_attr(g_serial_ctx.uart_bus, &g_serial_ctx.uart_attr); + #else + #error "SUPPORT_PPP_SERVICE_POLLING, SUPPORT_PPP_SERVICE_INTERRUPT, or SUPPORT_N706A_NET must be defined" + #endif + if (ret != ERRCODE_SUCC) { + PPP_DEBUG_ERROR("Failed to initialize UART: error code = %d (0x%x)", ret, ret); + PPP_DEBUG_ERROR("UART init failed with bus=%u, tx_pin=%u, rx_pin=%u, baudrate=%u", + g_serial_ctx.uart_bus, config->tx_pin, config->rx_pin, config->baudrate); + osal_kfree(g_serial_ctx.rx_buffer); + g_serial_ctx.rx_buffer = NULL; + return PPP_ERR_UART; + } + PPP_DEBUG_INFO("UART initialized successfully "); + + // 使能UART_BUS_0 (UART_L) 的唤醒 + hal_pmu_lpm_wakeup_action_enable(LPM_EVT_ACTION, TURN_ON, PMU_LPM_MCPU_UART_L0_RX_WAKEUP); + hal_pmu_lpm_wakeup_action_enable(LPM_INT_ACTION, TURN_ON, PMU_LPM_MCPU_UART_L0_RX_WAKEUP); + + /* 创建同步对象 */ + PPP_DEBUG_DEBUG("Creating synchronization objects..."); + if (osal_mutex_init(&g_serial_ctx.rx_mutex) != OSAL_SUCCESS) { + PPP_DEBUG_ERROR("Failed to create RX mutex"); + osal_kfree(g_serial_ctx.rx_buffer); + g_serial_ctx.rx_buffer = NULL; + uapi_uart_deinit(g_serial_ctx.uart_bus); + return PPP_ERR_MEMORY; + } + PPP_DEBUG_DEBUG("RX mutex created successfully"); + + if (osal_sem_init(&g_serial_ctx.rx_semaphore, 0) != OSAL_SUCCESS) { + PPP_DEBUG_ERROR("Failed to create RX semaphore"); + osal_mutex_destroy(&g_serial_ctx.rx_mutex); + osal_kfree(g_serial_ctx.rx_buffer); + g_serial_ctx.rx_buffer = NULL; + uapi_uart_deinit(g_serial_ctx.uart_bus); + return PPP_ERR_MEMORY; + } + PPP_DEBUG_DEBUG("RX semaphore created successfully"); + + /* 初始化缓冲区状态 */ + g_serial_ctx.rx_head = 0; + g_serial_ctx.rx_tail = 0; + g_serial_ctx.rx_count = 0; + g_serial_ctx.rx_callback = config->rx_callback; + g_serial_ctx.rx_callback_arg = config->rx_callback_arg; + + g_serial_ctx.initialized = true; + g_serial_ctx.current_mode = PPP_SERIAL_MODE_AT; + + /* 重置统计信息 */ + g_serial_ctx.tx_bytes = 0; + g_serial_ctx.rx_bytes = 0; + g_serial_ctx.tx_errors = 0; + g_serial_ctx.rx_errors = 0; + + /* 初始化智能flush策略参数 */ + g_serial_ctx.last_tx_time = ppp_serial_get_current_time_ms(); + g_serial_ctx.consecutive_small_packets = 0; + g_serial_ctx.idle_flush_threshold = 10; // 10ms空闲超时 + + PPP_DEBUG_DEBUG("Buffer state initialized:"); + PPP_DEBUG_DEBUG(" - RX Head/Tail/Count: %u/%u/%u", g_serial_ctx.rx_head, g_serial_ctx.rx_tail, g_serial_ctx.rx_count); + PPP_DEBUG_DEBUG(" - Current Mode: %d (AT)", g_serial_ctx.current_mode); + PPP_DEBUG_DEBUG(" - Initialized Flag: %s", g_serial_ctx.initialized ? "true" : "false"); + + PPP_DEBUG_INFO("PPP serial initialized successfully with unified buffer (%u bytes)", g_serial_ctx.rx_buffer_size); + PPP_DEBUG_INFO("=== PPP Serial Initialization Complete ==="); + return PPP_ERR_OK; +} + + +void check_cat1_uart_wakeup(void) +{ + PPP_DEBUG_DEBUG("Checking PPP UART wakeup..."); + // 检测唤醒状态 + uint16_t status = pmu_lpm_wakeup_get_status(LPM_EVT_ACTION, PMU_LPM_MCPU_UART_L0_RX_WAKEUP); + PPP_DEBUG_DEBUG("PPP UART wakeup status = %d", status); + if (status != 0) { + PPP_DEBUG_WARN("PPP UART wakeup detected, add cat1 sleep veto"); + if (pm_veto_get_id_cnt(PM_ID_CAT1) == 0) { + uapi_pm_add_sleep_veto(PM_ID_CAT1); + } + } +} + +/* ========== PPP数据模式切换函数已删除 ========== */ +/* N706A_NET 使用纯AT命令模式,始终处于AT模式,无需切换到PPP数据模式 */ + +/* ========== AT模式切换函数已删除 ========== */ +/* N706A_NET 始终处于AT模式,无需切换功能 */ +/* 如需清空缓冲区,请使用 ppp_serial_flush_rx() */ + +/** + * @brief 发送数据 - 两种模式都直接通过UART发送 + */ +ppp_error_t ppp_serial_write(const uint8_t *data, uint32_t length) +{ + if (!g_serial_ctx.initialized || !data || length == 0) { + PPP_DEBUG_ERROR("Invalid parameters: initialized=%s, data=%p, length=%u", + g_serial_ctx.initialized ? "true" : "false", data, length); + return PPP_ERR_INVALID_PARAM; + } + + PPP_DEBUG_DEBUG("=== PPP Serial Write Start ==="); + #if defined(PPP_LOW_POWER_MODE) && (PPP_LOW_POWER_MODE == 1) + ppp_api_wakeup_cat1(true); + osal_msleep(1); + #endif + PPP_DEBUG_DEBUG("Write request: %u bytes in mode %d", length, g_serial_ctx.current_mode); + PPP_DEBUG_DEBUG("UART Bus: %u", g_serial_ctx.uart_bus); + + ppp_error_t result = PPP_ERR_OK; + int32_t write_result = 0; + switch (g_serial_ctx.current_mode) { + case PPP_SERIAL_MODE_AT: + /* AT命令模式:直接使用UART发送 */ + PPP_DEBUG_DEBUG("Sending %u bytes via UART (AT mode)", length); + // PPP_DEBUG_HEXDUMP(data, length); + + PPP_DEBUG_DEBUG("Calling uapi_uart_write(bus=%u, data=%p, len=%u, timeout=1000)...", + g_serial_ctx.uart_bus, data, length, 1000); + + write_result = uapi_uart_write(g_serial_ctx.uart_bus, data, length, 1000); + PPP_DEBUG_DEBUG("uapi_uart_write returned: %d (expected: %u)", write_result, length); + + if (write_result != (int32_t)length) { + PPP_DEBUG_ERROR("UART write failed in AT mode: returned %d, expected %u", write_result, length); + PPP_DEBUG_ERROR("Possible causes: UART not initialized, hardware error, or timeout"); + g_serial_ctx.tx_errors++; + result = PPP_ERR_UART; + } else { + g_serial_ctx.tx_bytes += length; + PPP_DEBUG_INFO("Sent %u bytes successfully via UART (total tx: %u bytes)", + length, g_serial_ctx.tx_bytes); + } + break; + + case PPP_SERIAL_MODE_PPP_DATA: + /* PPP数据模式:直接使用UART发送(与AT模式一致) */ + PPP_DEBUG_INFO("Sending %u bytes via UART (PPP data mode)", length); + + /* 检查UART状态 */ + PPP_DEBUG_DEBUG("UART Bus: %u, Mode: PPP_DATA, TX Bytes: %u, TX Errors: %u", + g_serial_ctx.uart_bus, g_serial_ctx.tx_bytes, g_serial_ctx.tx_errors); + + /* 打印要发送的数据 */ + PPP_DEBUG_PRINT("PPP TX Data: "); + for (uint32_t i = 0; i < length && i < 16; i++) { + PPP_DEBUG_PRINT("%02X ", data[i]); + } + if (length > 16) { + PPP_DEBUG_PRINT("... (total %u bytes)", length); + } + PPP_DEBUG_PRINT("\n"); + + PPP_DEBUG_DEBUG("Calling uapi_uart_write(bus=%u, data=%p, len=%u, timeout=%d)...", + g_serial_ctx.uart_bus, data, length, PPP_OPTIMIZED_UART_WRITE_TIMEOUT); + + /* 优化UART写超时:使用配置的优化值以提高PPP协商速度 */ + write_result = uapi_uart_write(g_serial_ctx.uart_bus, data, length, PPP_OPTIMIZED_UART_WRITE_TIMEOUT); + PPP_DEBUG_INFO("uapi_uart_write returned: %d (expected: %u)", write_result, length); + + if (write_result != (int32_t)length) { + PPP_DEBUG_ERROR("UART write failed in PPP data mode: returned %d, expected %u", write_result, length); + PPP_DEBUG_ERROR("UART状态: bus=%u, 总发送字节=%u, 总错误=%u", + g_serial_ctx.uart_bus, g_serial_ctx.tx_bytes, g_serial_ctx.tx_errors); + PPP_DEBUG_ERROR("可能原因: UART未初始化、硬件错误、超时或缓冲区满"); + g_serial_ctx.tx_errors++; + result = PPP_ERR_UART; + } else { + g_serial_ctx.tx_bytes += length; + PPP_DEBUG_INFO("Sent %u bytes successfully via UART (PPP mode, total tx: %u bytes)", + length, g_serial_ctx.tx_bytes); + } + break; + + default: + PPP_DEBUG_ERROR("Invalid serial mode: %d", g_serial_ctx.current_mode); + result = PPP_ERR_INVALID_PARAM; + break; + } + + PPP_DEBUG_INFO("Write result: %s (error code: %d)", + result == PPP_ERR_OK ? "SUCCESS" : "FAILED", result); + PPP_DEBUG_INFO("Statistics: tx_bytes=%u, tx_errors=%u", g_serial_ctx.tx_bytes, g_serial_ctx.tx_errors); + PPP_DEBUG_INFO("=== PPP Serial Write End ==="); + #if defined(PPP_LOW_POWER_MODE) && (PPP_LOW_POWER_MODE == 1) + ppp_api_wakeup_cat1(false); + #endif + + return result; +} + +/** + * @brief 读取数据 - AT模式直接从UART读取,PPP模式从软件缓冲区读取 + */ +ppp_error_t ppp_serial_read(uint8_t *buffer, uint32_t buffer_size, uint32_t *bytes_read) +{ + if (!g_serial_ctx.initialized || !buffer || !bytes_read || buffer_size == 0) { + return PPP_ERR_INVALID_PARAM; + } + #if defined(PPP_LOW_POWER_MODE) && (PPP_LOW_POWER_MODE == 1) + ppp_api_wakeup_cat1(true); + osal_msleep(1); + #endif + + *bytes_read = 0; + ppp_error_t result = PPP_ERR_OK; + + switch (g_serial_ctx.current_mode) { + case PPP_SERIAL_MODE_AT: + /* AT命令模式:直接从UART读取 */ + { + + #if defined(CONFIG_UART_SUPPORT_DMA) + uart_write_dma_config_t dma_cfg = { + .src_width = UART_DMA_WIDTH, + .dest_width = UART_DMA_WIDTH, + .burst_length = UART_DMA_BURST_LENGTH, + .priority = UART_DMA_PRIORITY + }; + int32_t read_result = uapi_uart_read_by_dma(g_serial_ctx.uart_bus, buffer, buffer_size,&dma_cfg); + + #else + int32_t read_result = uapi_uart_read(g_serial_ctx.uart_bus, buffer, buffer_size, 100); + #endif + if (read_result > 0) { + *bytes_read = (uint32_t)read_result; + g_serial_ctx.rx_bytes += *bytes_read; + PPP_DEBUG_DEBUG("Read %u bytes via UART (AT mode)", *bytes_read); + } else if (read_result < 0) { + PPP_DEBUG_ERROR("UART read error: %d", read_result); + g_serial_ctx.rx_errors++; + result = PPP_ERR_UART; + } + } + break; + + case PPP_SERIAL_MODE_PPP_DATA: + /* PPP数据模式:使用优化的直接UART读取,平衡响应性和完整性 */ + { + /* 使用类似SLIP的高效读取策略: + * 1. 首次尝试非阻塞读取(1ms超时) + * 2. 如果有数据,继续快速读取直到无数据 + * 3. 这样可以批量读取连续到达的数据,提高效率 + */ + uint32_t total_read = 0; + const uint32_t MAX_BATCH_READ = 8; // 最多批量读取8次 + uint32_t batch_count = 0; + + while (batch_count < MAX_BATCH_READ && total_read < buffer_size) { + uint32_t remaining = buffer_size - total_read; + uint8_t *current_buffer = buffer + total_read; + + // 使用短超时进行读取,类似sio_tryread的1ms超时 + + #if defined(CONFIG_UART_SUPPORT_DMA) + uart_write_dma_config_t dma_cfg = { + .src_width = UART_DMA_WIDTH, + .dest_width = UART_DMA_WIDTH, + .burst_length = UART_DMA_BURST_LENGTH, + .priority = UART_DMA_PRIORITY + }; + int32_t read_result = uapi_uart_read_by_dma(g_serial_ctx.uart_bus, current_buffer, + remaining,&dma_cfg); + + #else + int32_t read_result = uapi_uart_read(g_serial_ctx.uart_bus, current_buffer, + remaining, batch_count == 0 ? 10 : 1); + #endif + if (read_result > 0) { + total_read += (uint32_t)read_result; + g_serial_ctx.rx_bytes += (uint32_t)read_result; + batch_count++; + + PPP_DEBUG_DEBUG("PPP batch read %u: got %d bytes (total: %u)", + batch_count, read_result, total_read); + + // 如果读取的数据少于请求的数据,说明没有更多数据了 + if ((uint32_t)read_result < remaining) { + break; + } + } else if (read_result == 0) { + // 超时,无更多数据 + break; + } else { + // 读取错误 + if (total_read == 0) { + PPP_DEBUG_ERROR("PPP UART Read: Error %d", read_result); + result = PPP_ERR_UART; + break; + } + break; + } + } + + *bytes_read = total_read; + + if (total_read > 0) { + PPP_DEBUG_DEBUG("PPP optimized read: %u bytes in %u batches", total_read, batch_count); + result = PPP_ERR_OK; + break; + } else { + result = PPP_ERR_OK; + break; // 无数据但无错误 + } + } + break; +#if 0 + case PPP_SERIAL_MODE_PPP_DATA: + /* PPP数据模式:从软件环形缓冲区读取 */ + osal_mutex_lock(&g_serial_ctx.rx_mutex); + + uint32_t available = ppp_serial_buffer_data_count(); + if (available == 0) { + osal_mutex_unlock(&g_serial_ctx.rx_mutex); + return PPP_ERR_OK; // 没有数据可读 + } + + uint32_t to_read = (available < buffer_size) ? available : buffer_size; + + for (uint32_t i = 0; i < to_read; i++) { + buffer[i] = g_serial_ctx.rx_buffer[g_serial_ctx.rx_tail]; + g_serial_ctx.rx_tail = (g_serial_ctx.rx_tail + 1) % g_serial_ctx.rx_buffer_size; + g_serial_ctx.rx_count--; + } + + *bytes_read = to_read; + osal_mutex_unlock(&g_serial_ctx.rx_mutex); + + PPP_DEBUG_DEBUG("Read %u bytes from software buffer (PPP data mode)", to_read); + // PPP_DEBUG_HEXDUMP(buffer, to_read); + break; +#endif + default: + PPP_DEBUG_ERROR("Invalid serial mode: %d", g_serial_ctx.current_mode); + result = PPP_ERR_INVALID_PARAM; + break; + } + + #if defined(PPP_LOW_POWER_MODE) && (PPP_LOW_POWER_MODE == 1) + ppp_api_wakeup_cat1(false); + osal_msleep(1); + #endif + return result; +} + +/** + * @brief PPP快速尝试读取 - 类似SLIP的sio_tryread,使用1ms超时 + * @param buffer 接收缓冲区 + * @param buffer_size 缓冲区大小 + * @param bytes_read 实际读取字节数 + * @return 错误码 + */ +ppp_error_t ppp_serial_tryread(uint8_t *buffer, uint32_t buffer_size, uint32_t *bytes_read) +{ + if (!g_serial_ctx.initialized || !buffer || !bytes_read || buffer_size == 0) { + return PPP_ERR_INVALID_PARAM; + } + + *bytes_read = 0; + + if (g_serial_ctx.current_mode == PPP_SERIAL_MODE_PPP_DATA) { + /* PPP数据模式:混合策略(先轮询检测,有数据时用 DMA) */ + #if defined(CONFIG_UART_SUPPORT_DMA) + // ⚠️ 核心问题:uapi_uart_read_by_dma 会阻塞 1 秒等待数据,不适合无数据时的轮询 + // 解决方案:先用普通读取(timeout=0)快速检测,有数据时再用 DMA 批量读取 + + // 第 1 步:非阻塞检测是否有数据(立即返回) + uint8_t probe_buf[8]; + int32_t probe_result = uapi_uart_read(g_serial_ctx.uart_bus, probe_buf, sizeof(probe_buf), 0); + + int32_t read_result = 0; + + if (probe_result > 0) { + // 有数据!先复制探测数据,再用 DMA 读取剩余数据 + static uint32_t dma_call_count = 0; + static uint32_t dma_success_count = 0; + + // 复制探测数据 + if ((uint32_t)probe_result <= buffer_size) { + memcpy(buffer, probe_buf, probe_result); + read_result = probe_result; + } + + // 如果 buffer 还有空间,尝试用 DMA 批量读取剩余数据 + if ((uint32_t)probe_result < buffer_size) { + uint32_t remaining = buffer_size - probe_result; + // DMA 读取策略:既然已确认有数据,就充分利用 DMA 批量传输能力 + // 只对足够大的块使用 DMA(>=64字节),小块数据用 DMA 开销反而更大 + uint32_t dma_size = (remaining >= 64) ? remaining : 0; + + if (dma_size > 0) { + uart_write_dma_config_t dma_cfg = { + .src_width = UART_DMA_WIDTH, + .dest_width = UART_DMA_WIDTH, + .burst_length = UART_DMA_BURST_LENGTH, + .priority = UART_DMA_PRIORITY + }; + + dma_call_count++; + if (dma_call_count == 1) { + PPP_DEBUG_WARN("=== Hybrid Mode: Poll + DMA ==="); + PPP_DEBUG_WARN("Poll detects data (instant), DMA reads bulk (8-64 bytes)"); + } + + int32_t dma_result = uapi_uart_read_by_dma(g_serial_ctx.uart_bus, + buffer + probe_result, + dma_size, &dma_cfg); + + if (dma_result > 0) { + read_result += dma_result; + dma_success_count++; + if (dma_success_count == 1 || dma_success_count % 50 == 0) { + PPP_DEBUG_WARN("DMA Success #%u: poll %d + DMA %d = %d bytes", + dma_success_count, probe_result, dma_result, read_result); + } + } else if (dma_result < 0 && dma_call_count <= 5) { + PPP_DEBUG_ERROR("DMA failed (%d), but poll got %d bytes", + dma_result, probe_result); + } + } + } + } else { + // 没有数据,立即返回(无阻塞) + read_result = 0; + } + + #else + int32_t read_result = uapi_uart_read(g_serial_ctx.uart_bus, buffer, buffer_size, 0); + #endif + if (read_result > 0) { + *bytes_read = (uint32_t)read_result; + g_serial_ctx.rx_bytes += *bytes_read; + return PPP_ERR_OK; + } else if (read_result == 0) { + /* 超时,无数据可读 */ + return PPP_ERR_OK; + } else { + /* UART读取错误 */ + PPP_DEBUG_ERROR("PPP UART tryread error: %d", read_result); + return PPP_ERR_UART; + } + } else { + /* AT模式:使用标准读取 */ + return ppp_serial_read(buffer, buffer_size, bytes_read); + } +} + +/** + * @brief 等待数据接收 + */ +ppp_error_t ppp_serial_wait_data(uint32_t timeout_ms) +{ + if (!g_serial_ctx.initialized) { + return PPP_ERR_INVALID_PARAM; + } + + /* 检查是否已有数据 */ + osal_mutex_lock(&g_serial_ctx.rx_mutex); + bool has_data = (g_serial_ctx.rx_count > 0); + osal_mutex_unlock(&g_serial_ctx.rx_mutex); + + if (has_data) { + return PPP_ERR_OK; + } + + /* 等待数据到达 */ + if (osal_sem_wait(&g_serial_ctx.rx_semaphore, timeout_ms) != OSAL_SUCCESS) { + return PPP_ERR_TIMEOUT; + } + + return PPP_ERR_OK; +} + +/** + * @brief 获取可读数据长度 + */ +uint32_t ppp_serial_available(void) +{ + if (!g_serial_ctx.initialized) { + return 0; + } + + osal_mutex_lock(&g_serial_ctx.rx_mutex); + uint32_t count = ppp_serial_buffer_data_count(); + osal_mutex_unlock(&g_serial_ctx.rx_mutex); + + return count; +} + +/* 智能flush策略实现函数 */ + +/** + * @brief 获取当前时间(毫秒) + */ +static uint32_t ppp_serial_get_current_time_ms(void) +{ + unsigned long long jiffies = osal_get_jiffies(); + return (uint32_t)osal_jiffies_to_msecs((unsigned int)jiffies); +} + +/* ========== PPP控制包检测函数已删除 ========== */ +/* N706A_NET 使用纯AT命令模式,不涉及PPP协议包 */ + +/** + * @brief 定时flush检查函数 + */ +/** + * @brief 清空接收缓冲区 + */ +void ppp_serial_flush_rx(void) +{ + if (!g_serial_ctx.initialized) { + return; + } + + osal_mutex_lock(&g_serial_ctx.rx_mutex); + g_serial_ctx.rx_head = 0; + g_serial_ctx.rx_tail = 0; + g_serial_ctx.rx_count = 0; + osal_mutex_unlock(&g_serial_ctx.rx_mutex); + + PPP_DEBUG_DEBUG("RX buffer flushed"); +} + +/** + * @brief 设置接收回调函数 + */ +ppp_error_t ppp_serial_set_rx_callback(ppp_serial_rx_callback_t callback, void *arg) +{ + if (!g_serial_ctx.initialized) { + return PPP_ERR_INVALID_PARAM; + } + + g_serial_ctx.rx_callback = callback; + g_serial_ctx.rx_callback_arg = arg; + + return PPP_ERR_OK; +} + +/** + * @brief 去初始化PPP串口 + */ +void ppp_serial_deinit(void) +{ + if (!g_serial_ctx.initialized) { + return; + } + + PPP_DEBUG_INFO("Deinitializing PPP serial, current mode: %d", g_serial_ctx.current_mode); + + /* 取消注册UART接收回调 */ + if (g_serial_ctx.current_mode == PPP_SERIAL_MODE_PPP_DATA) { + uapi_uart_unregister_rx_callback(g_serial_ctx.uart_bus); + } + + /* 关闭UART */ + uapi_uart_deinit(g_serial_ctx.uart_bus); + + /* 释放缓冲区 */ + if (g_serial_ctx.rx_buffer) { + osal_kfree(g_serial_ctx.rx_buffer); + g_serial_ctx.rx_buffer = NULL; + } + + /* 销毁同步对象 */ + osal_mutex_destroy(&g_serial_ctx.rx_mutex); + osal_sem_destroy(&g_serial_ctx.rx_semaphore); + + /* 清理上下文 */ + memset_s(&g_serial_ctx, sizeof(g_serial_ctx), 0, sizeof(g_serial_ctx)); + PPP_DEBUG_INFO("PPP serial deinitialized"); +} + +/** + * @brief 获取当前串口模式 + */ +ppp_serial_mode_t ppp_serial_get_mode(void) +{ + return g_serial_ctx.current_mode; +} + +/** + * @brief 获取统计信息 + */ +ppp_error_t ppp_serial_get_stats(ppp_serial_stats_t *stats) +{ + if (!g_serial_ctx.initialized || !stats) { + return PPP_ERR_INVALID_PARAM; + } + + stats->tx_bytes = g_serial_ctx.tx_bytes; + stats->rx_bytes = g_serial_ctx.rx_bytes; + stats->tx_errors = g_serial_ctx.tx_errors; + stats->rx_errors = g_serial_ctx.rx_errors; + + osal_mutex_lock(&g_serial_ctx.rx_mutex); + stats->rx_buffer_usage = g_serial_ctx.rx_count; + stats->rx_buffer_size = g_serial_ctx.rx_buffer_size; + osal_mutex_unlock(&g_serial_ctx.rx_mutex); + + // TX buffer removed - no longer used (direct UART write) + stats->tx_buffer_usage = 0; + stats->tx_buffer_size = 0; + + return PPP_ERR_OK; +} + +/** + * @brief 获取UART总线号 + * @return UART总线号,如果未初始化返回UART_BUS_0 + */ +uint8_t ppp_serial_get_uart_bus(void) +{ + return g_serial_ctx.uart_bus; +} + +/* 内部函数实现 */ + +/** + * @brief 串口接收回调函数 + */ +static void ppp_serial_rx_callback(const void *buffer, uint16_t length, bool remaining) +{ + (void)remaining; + + /* UART接收回调函数:将数据存储到环形缓冲区 */ + PPP_DEBUG_INFO("UART Callback: Received %u bytes", length); + + if (!buffer || length == 0) { + return; + } + + const uint8_t *data = (const uint8_t *)buffer; + + osal_mutex_lock(&g_serial_ctx.rx_mutex); + + /* 检查缓冲区空间 */ + uint32_t available_space = ppp_serial_buffer_available_space(); + if (available_space < length) { + PPP_DEBUG_WARN("RX buffer overflow, dropping %u bytes", length - available_space); + g_serial_ctx.rx_errors++; + length = available_space; + } + + /* 将数据复制到环形缓冲区 */ + for (uint16_t i = 0; i < length; i++) { + g_serial_ctx.rx_buffer[g_serial_ctx.rx_head] = data[i]; + g_serial_ctx.rx_head = (g_serial_ctx.rx_head + 1) % g_serial_ctx.rx_buffer_size; + g_serial_ctx.rx_count++; + } + + g_serial_ctx.rx_bytes += length; + + osal_mutex_unlock(&g_serial_ctx.rx_mutex); + + /* 通知有数据到达 */ + osal_sem_up(&g_serial_ctx.rx_semaphore); + + /* 调用用户回调函数 */ + if (g_serial_ctx.rx_callback) { + g_serial_ctx.rx_callback(data, length, g_serial_ctx.rx_callback_arg); + } + + PPP_DEBUG_INFO("UART Callback: Received %u bytes", length); + + // 打印前8字节用于调试 + PPP_DEBUG_PRINT("UART RX Data: "); + for (uint16_t i = 0; i < length && i < 8; i++) { + PPP_DEBUG_PRINT("%02X ", data[i]); + } + if (length > 8) { + PPP_DEBUG_PRINT("... (total %u bytes)", length); + } + PPP_DEBUG_PRINT("\n"); +} + +/** + * @brief 获取缓冲区可用空间 + */ +static uint32_t ppp_serial_buffer_available_space(void) +{ + return g_serial_ctx.rx_buffer_size - g_serial_ctx.rx_count; +} + +/** + * @brief 获取缓冲区数据数量 + */ +static uint32_t ppp_serial_buffer_data_count(void) +{ + return g_serial_ctx.rx_count; +} + +/** + * @brief 重置串口状态 + * @note TX buffer 已移除 - 发送改为直接UART写入 + */ +ppp_error_t ppp_serial_reset(void) +{ + PPP_DEBUG_INFO("=== Serial Reset Started ==="); + + if (!g_serial_ctx.initialized) { + PPP_DEBUG_WARN("Serial not initialized"); + return PPP_ERR_INVALID_STATE; + } + + // 1. 清空接收缓冲区 + PPP_DEBUG_INFO("Clearing RX buffer..."); + osal_mutex_lock(&g_serial_ctx.rx_mutex); + g_serial_ctx.rx_head = 0; + g_serial_ctx.rx_tail = 0; + g_serial_ctx.rx_count = 0; + osal_mutex_unlock(&g_serial_ctx.rx_mutex); + + // 2. 重置统计信息 + g_serial_ctx.tx_bytes = 0; + g_serial_ctx.rx_bytes = 0; + g_serial_ctx.tx_errors = 0; + g_serial_ctx.rx_errors = 0; + + // 3. 强制设置为AT模式 + g_serial_ctx.current_mode = PPP_SERIAL_MODE_AT; + + // 4. 短暂延迟让硬件稳定 + osal_msleep(50); + + PPP_DEBUG_INFO("=== Serial Reset Completed ==="); + return PPP_ERR_OK; +} + +/* + * 注意:以下IRQ处理函数暂时不使用,因为UART回调机制已经包含了中断处理 + * 如果需要更底层的中断控制,可以取消注释并调试IRQ冲突问题 + */ + +#if 0 +/** + * @brief UART中断处理函数 + */ +static void _ppp_serial_irq_handler(uint32_t irq_num, void *arg) +{ + (void)irq_num; + (void)arg; + + /* 调用HAL层的UART中断处理函数 */ + hal_uart_irq_handler(g_serial_ctx.uart_bus); +} + +/** + * @brief 注册UART中断 + */ +static ppp_error_t ppp_serial_register_irq(void) +{ + PPP_DEBUG_INFO("Registering UART IRQ %d for bus %u", SIO_UART_IRQ_NUM, g_serial_ctx.uart_bus); + + int32_t ret = osal_irq_request(SIO_UART_IRQ_NUM, _ppp_serial_irq_handler, NULL, "ppp_uart", 0); + if (ret != 0) { + PPP_DEBUG_ERROR("Failed to register UART IRQ %d: %d", SIO_UART_IRQ_NUM, ret); + return PPP_ERR_UART; + } + + PPP_DEBUG_INFO("UART IRQ %d registered successfully", SIO_UART_IRQ_NUM); + + osal_irq_set_priority(SIO_UART_IRQ_NUM, irq_prio(SIO_UART_IRQ_NUM)); + osal_irq_enable(SIO_UART_IRQ_NUM); + + return PPP_ERR_OK; +} + +/** + * @brief 注销UART中断 + */ +static ppp_error_t ppp_serial_unregister_irq(void) +{ + osal_irq_free(SIO_UART_IRQ_NUM, NULL); + osal_irq_disable(SIO_UART_IRQ_NUM); + PPP_DEBUG_INFO("UART IRQ %d unregistered successfully", SIO_UART_IRQ_NUM); + return PPP_ERR_OK; +} +#endif \ No newline at end of file diff --git a/code/sdk/zhihui/ppp/example/n706a_http_tcp_example.c b/code/sdk/zhihui/ppp/example/n706a_http_tcp_example.c deleted file mode 100644 index 074f6092a..000000000 --- a/code/sdk/zhihui/ppp/example/n706a_http_tcp_example.c +++ /dev/null @@ -1,375 +0,0 @@ -/** - * @file n706a_http_tcp_example.c - * @brief N706A HTTP客户端使用示例(基于TCP实现) - * - * 本示例展示如何使用基于TCP的HTTP/HTTPS客户端: - * - 支持6个并发连接 - * - HTTP使用普通TCP,HTTPS使用SSL TCP - * - 完全控制HTTP协议 - */ - -#include -#include -#include "n706a_http_client.h" -#include "ppp_client.h" - -/* ========== 示例1:基本HTTP GET请求 ========== */ -void example_http_get(void) -{ - printf("\n=== Example 1: Basic HTTP GET ===\n"); - - n706a_http_client_t client; - n706a_http_client_data_t data; - char response_buf[4096]; - char header_buf[1024]; - - /* 初始化HTTP客户端(使用socket 0) */ - n706a_http_client_init(&client, 0); - - /* 连接到HTTP服务器 */ - n706a_httpc_result_t ret = n706a_http_client_connect(&client, - "httpbin.org", - 80, // HTTP端口 - false // 不使用SSL - ); - - if (ret != N706A_HTTP_SUCCESS) { - printf("Failed to connect: %d\n", ret); - return; - } - - printf("Connected to httpbin.org:80\n"); - - /* 准备响应缓冲区 */ - memset(&data, 0, sizeof(data)); - data.responseBuf = response_buf; - data.responseBufLen = sizeof(response_buf); - data.headerBuf = header_buf; - data.headerBufLen = sizeof(header_buf); - - /* 发送GET请求 */ - ret = n706a_http_client_get(&client, "/get", &data); - - if (ret == N706A_HTTP_SUCCESS) { - printf("HTTP Status: %d\n", client.response_code); - printf("Response Headers:\n%s\n", header_buf); - printf("Response Body:\n%s\n", response_buf); - } else { - printf("GET request failed: %d\n", ret); - } - - /* 关闭连接 */ - n706a_http_client_close(&client); -} - -/* ========== 示例2:HTTPS GET请求 ========== */ -void example_https_get(void) -{ - printf("\n=== Example 2: HTTPS GET ===\n"); - - n706a_http_client_t client; - n706a_http_client_data_t data; - char response_buf[4096]; - - /* 初始化(使用socket 1) */ - n706a_http_client_init(&client, 1); - - /* 连接到HTTPS服务器 */ - n706a_httpc_result_t ret = n706a_http_client_connect(&client, - "api.github.com", - 443, // HTTPS端口 - true // 使用SSL - ); - - if (ret != N706A_HTTP_SUCCESS) { - printf("Failed to connect: %d\n", ret); - return; - } - - printf("Connected to api.github.com:443 (SSL)\n"); - - /* 准备响应缓冲区 */ - memset(&data, 0, sizeof(data)); - data.responseBuf = response_buf; - data.responseBufLen = sizeof(response_buf); - - /* 发送GET请求 */ - ret = n706a_http_client_get(&client, "/", &data); - - if (ret == N706A_HTTP_SUCCESS) { - printf("HTTP Status: %d\n", client.response_code); - printf("Response: %s\n", response_buf); - } else { - printf("GET request failed: %d\n", ret); - } - - n706a_http_client_close(&client); -} - -/* ========== 示例3:HTTP POST JSON数据 ========== */ -void example_http_post_json(void) -{ - printf("\n=== Example 3: HTTP POST with JSON ===\n"); - - n706a_http_client_t client; - n706a_http_client_data_t data; - char response_buf[4096]; - - /* 初始化 */ - n706a_http_client_init(&client, 2); - - /* 连接 */ - if (n706a_http_client_connect(&client, "httpbin.org", 80, false) != N706A_HTTP_SUCCESS) { - printf("Connection failed\n"); - return; - } - - /* 准备JSON数据 */ - const char *json_data = "{\"name\":\"N706A\",\"type\":\"4G Module\",\"vendor\":\"Neoway\"}"; - - /* 准备响应缓冲区 */ - memset(&data, 0, sizeof(data)); - data.responseBuf = response_buf; - data.responseBufLen = sizeof(response_buf); - - /* 发送POST请求 */ - n706a_httpc_result_t ret = n706a_http_client_post(&client, - "/post", - "application/json", - json_data, - strlen(json_data), - &data - ); - - if (ret == N706A_HTTP_SUCCESS) { - printf("HTTP Status: %d\n", client.response_code); - printf("Response: %s\n", response_buf); - } else { - printf("POST request failed: %d\n", ret); - } - - n706a_http_client_close(&client); -} - -/* ========== 示例4:自定义请求头 ========== */ -void example_custom_headers(void) -{ - printf("\n=== Example 4: Custom Headers ===\n"); - - n706a_http_client_t client; - n706a_http_client_data_t data; - char response_buf[4096]; - - n706a_http_client_init(&client, 0); - - /* 设置自定义请求头 */ - n706a_http_client_set_header(&client, - "X-API-Key: your-api-key-here\r\n" - "X-Custom-Header: custom-value\r\n" - ); - - /* 连接并请求 */ - if (n706a_http_client_connect(&client, "httpbin.org", 80, false) == N706A_HTTP_SUCCESS) { - memset(&data, 0, sizeof(data)); - data.responseBuf = response_buf; - data.responseBufLen = sizeof(response_buf); - - n706a_http_client_get(&client, "/headers", &data); - - printf("Response:\n%s\n", response_buf); - - n706a_http_client_close(&client); - } -} - -/* ========== 示例5:基本认证 ========== */ -void example_basic_auth(void) -{ - printf("\n=== Example 5: Basic Authentication ===\n"); - - n706a_http_client_t client; - n706a_http_client_data_t data; - char response_buf[4096]; - - n706a_http_client_init(&client, 0); - - /* 设置用户名和密码 */ - n706a_http_client_set_auth(&client, "user", "password"); - - /* 连接并请求 */ - if (n706a_http_client_connect(&client, "httpbin.org", 80, false) == N706A_HTTP_SUCCESS) { - memset(&data, 0, sizeof(data)); - data.responseBuf = response_buf; - data.responseBufLen = sizeof(response_buf); - - n706a_http_client_get(&client, "/basic-auth/user/password", &data); - - printf("HTTP Status: %d\n", client.response_code); - printf("Response: %s\n", response_buf); - - n706a_http_client_close(&client); - } -} - -/* ========== 示例6:Keep-Alive长连接 ========== */ -void example_keep_alive(void) -{ - printf("\n=== Example 6: Keep-Alive Connection ===\n"); - - n706a_http_client_t client; - n706a_http_client_data_t data; - char response_buf[4096]; - - n706a_http_client_init(&client, 0); - - /* 启用Keep-Alive */ - n706a_http_client_set_keepalive(&client, true); - - /* 连接一次,发送多个请求 */ - if (n706a_http_client_connect(&client, "httpbin.org", 80, false) == N706A_HTTP_SUCCESS) { - memset(&data, 0, sizeof(data)); - data.responseBuf = response_buf; - data.responseBufLen = sizeof(response_buf); - - /* 第一个请求 */ - printf("Request 1:\n"); - n706a_http_client_get(&client, "/get?page=1", &data); - printf("Status: %d\n", client.response_code); - - /* 第二个请求(复用连接) */ - printf("Request 2:\n"); - n706a_http_client_get(&client, "/get?page=2", &data); - printf("Status: %d\n", client.response_code); - - /* 第三个请求(复用连接) */ - printf("Request 3:\n"); - n706a_http_client_get(&client, "/get?page=3", &data); - printf("Status: %d\n", client.response_code); - - n706a_http_client_close(&client); - } -} - -/* ========== 示例7:并发HTTP请求(6个连接) ========== */ -void example_concurrent_requests(void) -{ - printf("\n=== Example 7: Concurrent HTTP Requests ===\n"); - - n706a_http_client_t clients[6]; - n706a_http_client_data_t data[6]; - char response_bufs[6][2048]; - const char *urls[] = { - "httpbin.org", "httpbin.org", "httpbin.org", - "httpbin.org", "httpbin.org", "httpbin.org" - }; - const char *paths[] = { - "/get?req=1", "/get?req=2", "/get?req=3", - "/get?req=4", "/get?req=5", "/get?req=6" - }; - - /* 初始化6个客户端 */ - for (int i = 0; i < 6; i++) { - n706a_http_client_init(&clients[i], i); // 使用socket 0-5 - - /* 连接 */ - if (n706a_http_client_connect(&clients[i], urls[i], 80, false) == N706A_HTTP_SUCCESS) { - printf("Client %d connected\n", i); - - /* 准备缓冲区 */ - memset(&data[i], 0, sizeof(data[i])); - data[i].responseBuf = response_bufs[i]; - data[i].responseBufLen = sizeof(response_bufs[i]); - - /* 发送请求 */ - n706a_http_client_get(&clients[i], paths[i], &data[i]); - printf("Client %d: Status %d\n", i, clients[i].response_code); - } - } - - /* 关闭所有连接 */ - for (int i = 0; i < 6; i++) { - n706a_http_client_close(&clients[i]); - } -} - -/* ========== 示例8:简化的URL接口 ========== */ -void example_simple_url_api(void) -{ - printf("\n=== Example 8: Simple URL API ===\n"); - - n706a_http_client_t client; - n706a_http_client_data_t data; - char response_buf[4096]; - - n706a_http_client_init(&client, 0); - - /* 准备响应缓冲区 */ - memset(&data, 0, sizeof(data)); - data.responseBuf = response_buf; - data.responseBufLen = sizeof(response_buf); - - /* 直接使用完整URL(自动解析协议、host、端口、路径) */ - n706a_httpc_result_t ret = n706a_http_client_get_url(&client, - "http://httpbin.org/get", - &data - ); - - if (ret == N706A_HTTP_SUCCESS) { - printf("Status: %d\n", client.response_code); - printf("Response: %s\n", response_buf); - } - - n706a_http_client_close(&client); - - /* HTTPS URL示例 */ - ret = n706a_http_client_get_url(&client, - "https://api.github.com/", - &data - ); - - if (ret == N706A_HTTP_SUCCESS) { - printf("HTTPS Status: %d\n", client.response_code); - } - - n706a_http_client_close(&client); -} - -/* ========== 主函数 ========== */ -int main(void) -{ - printf("N706A HTTP Client Examples (TCP-based)\n"); - printf("======================================\n"); - - /* 1. PPP拨号 */ - ppp_dial_config_t config = { - .apn = "cmnet", - .username = "", - .password = "", - .auth_type = PPP_AUTH_NONE - }; - - printf("Starting PPP dial...\n"); - if (ppp_client_dial(&config) != PPP_SUCCESS) { - printf("PPP dial failed\n"); - return -1; - } - printf("PPP connected\n\n"); - - /* 2. 运行示例 */ - example_http_get(); // 基本HTTP GET - example_https_get(); // HTTPS GET - example_http_post_json(); // HTTP POST JSON - example_custom_headers(); // 自定义请求头 - example_basic_auth(); // 基本认证 - example_keep_alive(); // Keep-Alive长连接 - example_concurrent_requests(); // 并发请求(6个) - example_simple_url_api(); // 简化URL API - - printf("\nAll examples completed\n"); - - /* 3. 断开PPP */ - ppp_client_hangup(); - - return 0; -} - diff --git a/code/sdk/zhihui/ppp/example/n706a_ssl_comparison_example.c b/code/sdk/zhihui/ppp/example/n706a_ssl_comparison_example.c deleted file mode 100644 index 64cb65aed..000000000 --- a/code/sdk/zhihui/ppp/example/n706a_ssl_comparison_example.c +++ /dev/null @@ -1,395 +0,0 @@ -/** - * @file n706a_ssl_comparison_example.c - * @brief N706A SSL实现方式对比示例 - * - * 展示两种SSL实现方式的使用: - * - AT指令实现(N706A_SSL_USE_AT_COMMAND) - * - mbedtls实现(N706A_SSL_USE_MBEDTLS) - * - * 编译方法: - * gcc -DN706A_SSL_USE_AT_COMMAND n706a_ssl_comparison_example.c # AT指令 - * gcc -DN706A_SSL_USE_MBEDTLS n706a_ssl_comparison_example.c -lmbedtls # mbedtls - */ - -#include -#include -#include "n706a_tcp_client.h" -#include "ppp_client.h" - -/* ========== 示例1:基本SSL连接(两种实现API相同) ========== */ -void example_basic_ssl_connection(void) -{ - printf("\n=== Example 1: Basic SSL Connection ===\n"); - -#ifdef N706A_SSL_USE_AT_COMMAND - printf("Using: AT Command SSL Implementation\n"); -#endif -#ifdef N706A_SSL_USE_MBEDTLS - printf("Using: mbedtls SSL Implementation\n"); -#endif - - n706a_tcp_socket_t sock; - - /* 创建socket */ - n706a_tcp_socket_create(&sock, 0); - - /* 配置SSL(可选,使用默认也可以) */ -#ifdef N706A_SSL_USE_AT_COMMAND - /* AT指令模式:使用文件名 */ - n706a_tcp_config_ssl(&sock, - 3, // TLS 1.2 - 1, // 服务器认证 - "ca.crt" // 证书文件名(已上传到模组) - ); -#endif - -#ifdef N706A_SSL_USE_MBEDTLS - /* mbedtls模式:使用PEM字符串 */ - const char *ca_pem = - "-----BEGIN CERTIFICATE-----\n" - "MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh\n" - // ... 证书内容 ... - "-----END CERTIFICATE-----\n"; - - n706a_tcp_config_ssl(&sock, - MBEDTLS_SSL_MINOR_VERSION_3, // TLS 1.2 - MBEDTLS_SSL_VERIFY_REQUIRED, // 必须验证 - ca_pem // PEM字符串 - ); -#endif - - /* 建立SSL连接(API完全相同) */ - n706a_tcp_result_t ret = n706a_tcp_connect(&sock, - "www.google.com", - 443, - true, // 使用SSL - 30000 // 30秒超时 - ); - - if (ret == N706A_TCP_SUCCESS) { - printf("SSL connected successfully!\n"); - - /* 发送HTTPS GET请求 */ - const char *request = - "GET / HTTP/1.1\r\n" - "Host: www.google.com\r\n" - "Connection: close\r\n\r\n"; - - size_t sent; - n706a_tcp_send(&sock, (uint8_t*)request, strlen(request), &sent); - printf("Sent %zu bytes\n", sent); - - /* 接收响应 */ - uint8_t buffer[2048]; - size_t received; - n706a_tcp_recv(&sock, buffer, sizeof(buffer), &received, 10000); - printf("Received %zu bytes:\n%.*s\n", received, (int)received, buffer); - - /* 关闭连接 */ - n706a_tcp_close(&sock); - } else { - printf("SSL connection failed: %d\n", ret); - } -} - -/* ========== 示例2:性能测试 ========== */ -void example_performance_test(void) -{ - printf("\n=== Example 2: Performance Test ===\n"); - -#ifdef N706A_SSL_USE_AT_COMMAND - printf("Testing AT Command SSL Performance\n"); -#endif -#ifdef N706A_SSL_USE_MBEDTLS - printf("Testing mbedtls SSL Performance\n"); -#endif - - n706a_tcp_socket_t sock; - n706a_tcp_socket_create(&sock, 0); - - /* 测试SSL握手时间 */ - uint32_t start_time = get_tick_ms(); // 假设有这个函数 - - n706a_tcp_result_t ret = n706a_tcp_connect(&sock, - "www.example.com", - 443, - true, - 30000 - ); - - uint32_t handshake_time = get_tick_ms() - start_time; - - if (ret == N706A_TCP_SUCCESS) { - printf("SSL handshake time: %u ms\n", handshake_time); - - /* 测试数据传输速度 */ - const char *test_data = "This is a test message for SSL performance!\n"; - size_t data_len = strlen(test_data); - - start_time = get_tick_ms(); - - /* 发送100次 */ - for (int i = 0; i < 100; i++) { - size_t sent; - n706a_tcp_send(&sock, (uint8_t*)test_data, data_len, &sent); - } - - uint32_t send_time = get_tick_ms() - start_time; - float throughput = (data_len * 100 * 1000.0) / send_time / 1024.0; // KB/s - - printf("Send throughput: %.2f KB/s\n", throughput); - - n706a_tcp_close(&sock); - } -} - -/* ========== 示例3:内存占用测试 ========== */ -void example_memory_usage(void) -{ - printf("\n=== Example 3: Memory Usage ===\n"); - - printf("sizeof(n706a_tcp_socket_t) = %zu bytes\n", sizeof(n706a_tcp_socket_t)); - -#ifdef N706A_SSL_USE_AT_COMMAND - printf("AT Command mode:\n"); - printf(" - Socket structure: ~%zu bytes\n", sizeof(n706a_tcp_socket_t)); - printf(" - Additional heap: ~2KB (estimated)\n"); - printf(" - Total per connection: ~%zu bytes\n", sizeof(n706a_tcp_socket_t) + 2048); -#endif - -#ifdef N706A_SSL_USE_MBEDTLS - printf("mbedtls mode:\n"); - printf(" - Socket structure: ~%zu bytes\n", sizeof(n706a_tcp_socket_t)); - printf(" - Includes mbedtls contexts\n"); - printf(" - Additional buffers: ~20KB (estimated)\n"); - printf(" - Total per connection: ~%zu bytes\n", sizeof(n706a_tcp_socket_t) + 20480); -#endif - - /* 测试6个并发SSL连接的内存 */ - printf("\n6 concurrent SSL connections:\n"); - -#ifdef N706A_SSL_USE_AT_COMMAND - size_t total_memory = (sizeof(n706a_tcp_socket_t) + 2048) * 6; - printf(" AT Command: ~%zu bytes (%.1f KB)\n", - total_memory, total_memory / 1024.0); -#endif - -#ifdef N706A_SSL_USE_MBEDTLS - size_t total_memory = (sizeof(n706a_tcp_socket_t) + 20480) * 6; - printf(" mbedtls: ~%zu bytes (%.1f KB)\n", - total_memory, total_memory / 1024.0); -#endif -} - -/* ========== 示例4:AT指令特有功能 ========== */ -#ifdef N706A_SSL_USE_AT_COMMAND -void example_at_specific_features(void) -{ - printf("\n=== Example 4: AT Command Specific Features ===\n"); - - n706a_tcp_socket_t sock; - n706a_tcp_socket_create(&sock, 0); - - /* AT指令模式可以使用模组文件系统中的证书 */ - printf("Using certificate from modem file system\n"); - - n706a_tcp_config_ssl(&sock, - 3, // TLS 1.2 - 1, // 服务器认证 - "ca.crt" // 文件名 - ); - - /* 建立连接 */ - n706a_tcp_result_t ret = n706a_tcp_connect(&sock, - "secure.example.com", - 443, - true, - 30000 - ); - - if (ret == N706A_TCP_SUCCESS) { - printf("Connected using modem SSL hardware acceleration\n"); - printf("Benefits:\n"); - printf(" - Fast SSL processing (hardware accelerated)\n"); - printf(" - Low CPU usage on host MCU\n"); - printf(" - Small memory footprint\n"); - - n706a_tcp_close(&sock); - } -} -#endif - -/* ========== 示例5:mbedtls特有功能 ========== */ -#ifdef N706A_SSL_USE_MBEDTLS -void example_mbedtls_specific_features(void) -{ - printf("\n=== Example 5: mbedtls Specific Features ===\n"); - - n706a_tcp_socket_t sock; - n706a_tcp_socket_create(&sock, 0); - - /* mbedtls模式:PEM格式证书在内存中 */ - const char *ca_pem = "-----BEGIN CERTIFICATE-----\n...\n"; - - n706a_tcp_config_ssl(&sock, - MBEDTLS_SSL_MINOR_VERSION_3, - MBEDTLS_SSL_VERIFY_REQUIRED, - ca_pem - ); - - /* mbedtls模式可以配置客户端证书(双向认证) */ - const char *client_cert = "-----BEGIN CERTIFICATE-----\n...\n"; - const char *client_key = "-----BEGIN RSA PRIVATE KEY-----\n...\n"; - - n706a_tcp_config_ssl_client_cert(&sock, client_cert, client_key); - - /* 建立连接 */ - n706a_tcp_result_t ret = n706a_tcp_connect(&sock, - "secure.example.com", - 443, - true, - 30000 - ); - - if (ret == N706A_TCP_SUCCESS) { - printf("Connected using mbedtls\n"); - printf("Benefits:\n"); - printf(" - Full control over SSL parameters\n"); - printf(" - Can customize cipher suites\n"); - printf(" - Easy debugging (code on host)\n"); - printf(" - Platform independent\n"); - - /* 可以直接访问SSL上下文进行高级操作 */ - printf("\nSSL Info:\n"); - printf(" Protocol: %s\n", mbedtls_ssl_get_version(&sock.ssl)); - printf(" Cipher: %s\n", mbedtls_ssl_get_ciphersuite(&sock.ssl)); - - /* 自定义密码套件 */ - const int ciphersuites[] = { - MBEDTLS_TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, - MBEDTLS_TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, - 0 - }; - mbedtls_ssl_conf_ciphersuites(&sock.conf, ciphersuites); - printf(" Custom ciphersuites configured\n"); - - n706a_tcp_close(&sock); - } -} -#endif - -/* ========== 示例6:并发SSL连接 ========== */ -void example_concurrent_ssl_connections(void) -{ - printf("\n=== Example 6: Concurrent SSL Connections ===\n"); - -#ifdef N706A_SSL_USE_AT_COMMAND - printf("Testing 6 concurrent SSL connections (AT Command)\n"); - int max_connections = 6; // AT指令模式可以支持6个 -#endif - -#ifdef N706A_SSL_USE_MBEDTLS - printf("Testing 2 concurrent SSL connections (mbedtls)\n"); - int max_connections = 2; // mbedtls模式建议最多2个(内存限制) -#endif - - n706a_tcp_socket_t socks[6]; - int connected = 0; - - /* 尝试建立多个SSL连接 */ - for (int i = 0; i < max_connections; i++) { - n706a_tcp_socket_create(&socks[i], i); - - n706a_tcp_result_t ret = n706a_tcp_connect(&socks[i], - "www.example.com", - 443, - true, - 30000 - ); - - if (ret == N706A_TCP_SUCCESS) { - connected++; - printf("Connection %d: SUCCESS\n", i); - } else { - printf("Connection %d: FAILED\n", i); - } - } - - printf("\nTotal connected: %d/%d\n", connected, max_connections); - -#ifdef N706A_SSL_USE_AT_COMMAND - printf("AT Command mode: Can handle 6 concurrent SSL connections easily\n"); -#endif - -#ifdef N706A_SSL_USE_MBEDTLS - printf("mbedtls mode: Limited by memory, recommend 1-2 connections\n"); -#endif - - /* 关闭所有连接 */ - for (int i = 0; i < connected; i++) { - n706a_tcp_close(&socks[i]); - } -} - -/* ========== 主函数 ========== */ -int main(void) -{ - printf("==============================================\n"); - printf("N706A SSL Implementation Comparison\n"); - printf("==============================================\n"); - -#ifdef N706A_SSL_USE_AT_COMMAND - printf("Current build: AT Command SSL Implementation\n"); - printf(" Recommended for: Standard applications\n"); - printf(" Pros: Fast, low memory, reliable\n"); - printf(" Cons: Limited customization\n"); -#endif - -#ifdef N706A_SSL_USE_MBEDTLS - printf("Current build: mbedtls SSL Implementation\n"); - printf(" Recommended for: Custom requirements\n"); - printf(" Pros: Full control, flexible\n"); - printf(" Cons: Slower, more memory\n"); -#endif - - printf("==============================================\n\n"); - - /* PPP拨号 */ - ppp_dial_config_t config = { - .apn = "cmnet", - .username = "", - .password = "", - .auth_type = PPP_AUTH_NONE - }; - - if (ppp_client_dial(&config) != PPP_SUCCESS) { - printf("PPP dial failed\n"); - return -1; - } - printf("PPP connected\n\n"); - - /* 运行示例 */ - example_basic_ssl_connection(); - example_performance_test(); - example_memory_usage(); - -#ifdef N706A_SSL_USE_AT_COMMAND - example_at_specific_features(); -#endif - -#ifdef N706A_SSL_USE_MBEDTLS - example_mbedtls_specific_features(); -#endif - - example_concurrent_ssl_connections(); - - printf("\n==============================================\n"); - printf("All examples completed\n"); - printf("==============================================\n"); - - /* 断开PPP */ - ppp_client_hangup(); - - return 0; -} - diff --git a/code/sdk/zhihui/ppp/example/n706a_websocket_example.c b/code/sdk/zhihui/ppp/example/n706a_websocket_example.c deleted file mode 100644 index 5c3f7926d..000000000 --- a/code/sdk/zhihui/ppp/example/n706a_websocket_example.c +++ /dev/null @@ -1,397 +0,0 @@ -/** - * @file n706a_websocket_example.c - * @brief N706A WebSocket客户端使用示例 - * - * 展示如何使用WebSocket客户端: - * - ws://普通连接 - * - wss://安全连接 - * - 发送文本和二进制消息 - * - 接收消息回调 - */ - -#include -#include -#include -#include "n706a_websocket.h" -#include "ppp_client.h" - -/* ========== 回调函数 ========== */ - -void on_open(int client_id) -{ - printf("[Client %d] WebSocket connected!\n", client_id); -} - -void on_message(int client_id, const char *data, size_t length, - bool is_finished, bool is_binary) -{ - printf("[Client %d] Message received (%zu bytes, %s, %s):\n", - client_id, length, - is_binary ? "binary" : "text", - is_finished ? "finished" : "partial"); - - if (!is_binary && length < 1024) { - printf(" Content: %.*s\n", (int)length, data); - } else { - printf(" Content: [binary data]\n"); - } -} - -void on_error(int client_id, int error_code, const char *error_msg) -{ - printf("[Client %d] Error %d: %s\n", client_id, error_code, error_msg); -} - -void on_close(int client_id, int close_code, const char *reason) -{ - printf("[Client %d] Closed (code=%d, reason=%s)\n", - client_id, close_code, reason ? reason : "none"); -} - -/* ========== 示例1:基本WebSocket连接 ========== */ -void example_basic_websocket(void) -{ - printf("\n=== Example 1: Basic WebSocket (ws://) ===\n"); - - /* 创建客户端 */ - int client = n706a_websocket_constructor( - on_open, - on_message, - on_error, - on_close - ); - - if (client < 0) { - printf("Failed to create WebSocket client\n"); - return; - } - - printf("Client ID: %d\n", client); - - /* 连接到WebSocket服务器 */ - int ret = n706a_websocket_connect(client, - "ws://echo.websocket.org/", - NULL - ); - - if (ret == N706A_WS_SUCCESS) { - printf("Connected to echo server\n"); - - /* 发送文本消息 */ - n706a_websocket_send_text(client, "Hello WebSocket!", 16); - printf("Sent text message\n"); - - /* 等待回显 */ - sleep(2); - - /* 发送更多消息 */ - n706a_websocket_send_text(client, "This is N706A speaking", 22); - sleep(2); - - /* 关闭连接 */ - n706a_websocket_close(client); - sleep(1); - } else { - printf("Connection failed: %d\n", ret); - } - - /* 销毁客户端 */ - n706a_websocket_destructor(client); -} - -/* ========== 示例2:安全WebSocket连接(WSS) ========== */ -void example_secure_websocket(void) -{ - printf("\n=== Example 2: Secure WebSocket (wss://) ===\n"); - - int client = n706a_websocket_constructor( - on_open, on_message, on_error, on_close - ); - - if (client < 0) { - printf("Failed to create client\n"); - return; - } - - /* 连接到WSS服务器 */ - int ret = n706a_websocket_connect(client, - "wss://echo.websocket.org/", - NULL - ); - - if (ret == N706A_WS_SUCCESS) { - printf("Secure connection established (SSL/TLS)\n"); - - /* 发送消息 */ - n706a_websocket_send_text(client, "Secure Hello!", 13); - - sleep(3); - - n706a_websocket_close(client); - sleep(1); - } - - n706a_websocket_destructor(client); -} - -/* ========== 示例3:发送二进制数据 ========== */ -void example_binary_data(void) -{ - printf("\n=== Example 3: Binary Data ===\n"); - - int client = n706a_websocket_constructor( - on_open, on_message, on_error, on_close - ); - - if (client < 0) return; - - if (n706a_websocket_connect(client, "ws://echo.websocket.org/", NULL) - == N706A_WS_SUCCESS) { - - /* 创建二进制数据 */ - uint8_t binary_data[] = {0x01, 0x02, 0x03, 0x04, 0x05, - 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF}; - - /* 发送二进制消息 */ - n706a_websocket_send_binary(client, binary_data, sizeof(binary_data)); - printf("Sent binary data (%zu bytes)\n", sizeof(binary_data)); - - sleep(2); - - n706a_websocket_close(client); - sleep(1); - } - - n706a_websocket_destructor(client); -} - -/* ========== 示例4:Ping/Pong ========== */ -void example_ping_pong(void) -{ - printf("\n=== Example 4: Ping/Pong ===\n"); - - int client = n706a_websocket_constructor( - on_open, on_message, on_error, on_close - ); - - if (client < 0) return; - - if (n706a_websocket_connect(client, "ws://echo.websocket.org/", NULL) - == N706A_WS_SUCCESS) { - - /* 发送Ping */ - n706a_websocket_send_ping(client, "ping", 4); - printf("Sent Ping\n"); - - sleep(1); - - /* 服务器应该回复Pong */ - /* 发送普通消息 */ - n706a_websocket_send_text(client, "After ping", 10); - - sleep(2); - - n706a_websocket_close(client); - sleep(1); - } - - n706a_websocket_destructor(client); -} - -/* ========== 示例5:多个并发WebSocket连接 ========== */ -void example_multiple_connections(void) -{ - printf("\n=== Example 5: Multiple WebSocket Connections ===\n"); - - int clients[3]; - const char *urls[] = { - "ws://echo.websocket.org/", - "ws://echo.websocket.org/", - "wss://echo.websocket.org/" - }; - - /* 创建3个客户端 */ - for (int i = 0; i < 3; i++) { - clients[i] = n706a_websocket_constructor( - on_open, on_message, on_error, on_close - ); - - if (clients[i] < 0) { - printf("Failed to create client %d\n", i); - continue; - } - - /* 连接 */ - int ret = n706a_websocket_connect(clients[i], urls[i], NULL); - if (ret == N706A_WS_SUCCESS) { - printf("Client %d connected to %s\n", clients[i], urls[i]); - } else { - printf("Client %d connection failed\n", clients[i]); - } - } - - sleep(1); - - /* 所有客户端发送消息 */ - for (int i = 0; i < 3; i++) { - if (n706a_websocket_is_connected(clients[i])) { - char msg[64]; - snprintf(msg, sizeof(msg), "Message from client %d", clients[i]); - n706a_websocket_send_text(clients[i], msg, strlen(msg)); - } - } - - sleep(3); - - /* 关闭所有连接 */ - for (int i = 0; i < 3; i++) { - n706a_websocket_close(clients[i]); - n706a_websocket_destructor(clients[i]); - } - - sleep(1); -} - -/* ========== 示例6:优雅关闭 ========== */ -void example_graceful_close(void) -{ - printf("\n=== Example 6: Graceful Close ===\n"); - - int client = n706a_websocket_constructor( - on_open, on_message, on_error, on_close - ); - - if (client < 0) return; - - if (n706a_websocket_connect(client, "ws://echo.websocket.org/", NULL) - == N706A_WS_SUCCESS) { - - /* 发送消息 */ - n706a_websocket_send_text(client, "Goodbye!", 8); - - sleep(1); - - /* 优雅关闭(带原因) */ - n706a_websocket_close_with_reason(client, - N706A_WS_CLOSE_NORMAL, - "Client closing normally" - ); - - printf("Sent close frame\n"); - - sleep(2); - } - - n706a_websocket_destructor(client); -} - -/* ========== 示例7:实时数据流 ========== */ -void example_realtime_stream(void) -{ - printf("\n=== Example 7: Realtime Data Stream ===\n"); - - int client = n706a_websocket_constructor( - on_open, on_message, on_error, on_close - ); - - if (client < 0) return; - - /* 连接到实时数据服务器(这里用echo演示) */ - if (n706a_websocket_connect(client, "ws://echo.websocket.org/", NULL) - == N706A_WS_SUCCESS) { - - printf("Streaming data for 10 seconds...\n"); - - /* 持续发送数据 */ - for (int i = 0; i < 10; i++) { - char data[128]; - snprintf(data, sizeof(data), - "{\"seq\":%d,\"timestamp\":%ld,\"value\":%d}", - i, time(NULL), rand() % 100); - - n706a_websocket_send_text(client, data, strlen(data)); - printf("Sent: %s\n", data); - - sleep(1); - } - - n706a_websocket_close(client); - sleep(1); - } - - n706a_websocket_destructor(client); -} - -/* ========== 示例8:指定Socket ID ========== */ -void example_with_specific_socket(void) -{ - printf("\n=== Example 8: Specify Socket ID ===\n"); - - /* 使用socket 3 */ - int client = n706a_websocket_constructor_with_socket(3, - on_open, on_message, on_error, on_close - ); - - if (client < 0) { - printf("Failed to create client with socket 3\n"); - return; - } - - printf("Created client using socket ID 3\n"); - - if (n706a_websocket_connect(client, "ws://echo.websocket.org/", NULL) - == N706A_WS_SUCCESS) { - - n706a_websocket_send_text(client, "Using socket 3", 14); - - sleep(2); - - n706a_websocket_close(client); - sleep(1); - } - - n706a_websocket_destructor(client); -} - -/* ========== 主函数 ========== */ -int main(void) -{ - printf("==============================================\n"); - printf("N706A WebSocket Client Examples\n"); - printf("==============================================\n"); - - /* PPP拨号 */ - ppp_dial_config_t config = { - .apn = "cmnet", - .username = "", - .password = "", - .auth_type = PPP_AUTH_NONE - }; - - printf("Starting PPP dial...\n"); - if (ppp_client_dial(&config) != PPP_SUCCESS) { - printf("PPP dial failed\n"); - return -1; - } - printf("PPP connected\n\n"); - - /* 运行示例 */ - example_basic_websocket(); // WS基本连接 - example_secure_websocket(); // WSS安全连接 - example_binary_data(); // 二进制数据 - example_ping_pong(); // Ping/Pong - example_multiple_connections(); // 多连接 - example_graceful_close(); // 优雅关闭 - example_realtime_stream(); // 实时数据流 - example_with_specific_socket(); // 指定Socket ID - - printf("\n==============================================\n"); - printf("All examples completed\n"); - printf("==============================================\n"); - - /* 断开PPP */ - ppp_client_hangup(); - - return 0; -} - diff --git a/code/sdk/zhihui/ppp/include/n706a_http_client.h b/code/sdk/zhihui/ppp/include/n706a_http_client.h deleted file mode 100644 index 919ff4e00..000000000 --- a/code/sdk/zhihui/ppp/include/n706a_http_client.h +++ /dev/null @@ -1,271 +0,0 @@ -/** - * @file n706a_http_client.h - * @brief N706A HTTP/HTTPS客户端接口(基于TCP实现) - * - * 本实现采用统一的TCP底层架构: - * - HTTP通过普通TCP连接实现 - * - HTTPS通过SSL TCP连接实现 - * - 手动构造HTTP协议,突破AT+HTTP*单连接限制 - * - 支持6个并发HTTP/HTTPS连接 - * - * @version 3.0 - * @date 2025-10-30 - */ - -#ifndef N706A_HTTP_CLIENT_H -#define N706A_HTTP_CLIENT_H - -#include -#include -#include -#include "n706a_tcp_client.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * @brief N706A HTTP请求类型 - */ -typedef enum { - N706A_HTTP_DELETE, - N706A_HTTP_GET, - N706A_HTTP_HEAD, - N706A_HTTP_POST, - N706A_HTTP_PUT -} n706a_http_request_type_t; - -/** - * @brief N706A HTTP错误码 - */ -typedef enum { - N706A_HTTP_EAGAIN = 1, /**< 还有更多数据需要获取 */ - N706A_HTTP_SUCCESS = 0, /**< 操作成功 */ - N706A_HTTP_ENOBUFS = -1, /**< 缓冲区错误 */ - N706A_HTTP_EARG = -2, /**< 非法参数 */ - N706A_HTTP_ENOTSUPP = -3, /**< 不支持 */ - N706A_HTTP_EDNS = -4, /**< DNS失败 */ - N706A_HTTP_ECONN = -5, /**< 连接失败 */ - N706A_HTTP_ESEND = -6, /**< 发送数据包失败 */ - N706A_HTTP_ECLSD = -7, /**< 连接已关闭 */ - N706A_HTTP_ERECV = -8, /**< 接收数据包失败 */ - N706A_HTTP_EPARSE = -9, /**< URL解析错误 */ - N706A_HTTP_EPROTO = -10, /**< 协议错误 */ - N706A_HTTP_EUNKOWN = -11, /**< 未知错误 */ - N706A_HTTP_ETIMEOUT = -12, /**< 超时 */ -} n706a_httpc_result_t; - -/** - * @brief N706A HTTP客户端结构(基于TCP实现) - */ -typedef struct { - n706a_tcp_socket_t tcp_sock; /**< 底层TCP socket */ - int socket_id; /**< TCP socket编号(0-5) */ - bool is_https; /**< HTTPS标志(决定是否使用SSL) */ - uint16_t remote_port; /**< 远程端口 */ - int response_code; /**< HTTP响应码 */ - char *header; /**< 自定义请求头 */ - char *auth_user; /**< 基本认证用户名 */ - char *auth_password; /**< 基本认证密码 */ - char host[256]; /**< 主机名 */ - bool connected; /**< 连接状态 */ - bool keepalive; /**< 长连接模式 */ - uint32_t timeout_ms; /**< 超时时间(毫秒) */ -} n706a_http_client_t; - -/** - * @brief N706A HTTP数据结构 - */ -typedef struct { - bool isMore; /**< 是否还有更多数据需要获取 */ - bool isChunked; /**< 响应数据是否分块编码 */ - int method; /**< HTTP方法 */ - int retrieveLen; /**< 需要获取的内容长度 */ - int responseContentLen; /**< 响应内容总长度 */ - int contentBlockLen; /**< 一个块的内容长度 */ - int postBufLen; /**< POST数据长度 */ - int responseBufLen; /**< 响应体缓冲区长度 */ - int headerBufLen; /**< 响应头缓冲区长度 */ - char *postContentType; /**< POST数据的内容类型 */ - char *postBuf; /**< 要POST的用户数据 */ - char *responseBuf; /**< 存储响应体数据的缓冲区 */ - char *headerBuf; /**< 存储响应头数据的缓冲区 */ - bool isRedirected; /**< 是否重定向 */ - char *redirectUrl; /**< 重定向URL */ -} n706a_http_client_data_t; - -/** - * @brief 初始化N706A HTTP客户端 - * @param client HTTP客户端结构指针 - * @param socket_id TCP socket编号(0-5) - * @return N706A_HTTP_SUCCESS成功,其他失败 - */ -n706a_httpc_result_t n706a_http_client_init(n706a_http_client_t *client, int socket_id); - -/** - * @brief 连接到HTTP/HTTPS服务器 - * @param client HTTP客户端结构指针 - * @param host 主机名或IP地址 - * @param port 端口号(HTTP默认80,HTTPS默认443) - * @param use_ssl 是否使用SSL(HTTPS) - * @return N706A_HTTP_SUCCESS成功,其他失败 - */ -n706a_httpc_result_t n706a_http_client_connect(n706a_http_client_t *client, - const char *host, - uint16_t port, - bool use_ssl); - -/** - * @brief 发送HTTP GET请求 - * @param client HTTP客户端结构指针 - * @param path 请求路径(如"/api/data") - * @param client_data 响应数据结构指针 - * @return N706A_HTTP_SUCCESS成功,其他失败 - */ -n706a_httpc_result_t n706a_http_client_get(n706a_http_client_t *client, - const char *path, - n706a_http_client_data_t *client_data); - -/** - * @brief 发送HTTP POST请求 - * @param client HTTP客户端结构指针 - * @param path 请求路径 - * @param content_type 内容类型(如"application/json") - * @param body 请求体数据 - * @param body_len 请求体长度 - * @param client_data 响应数据结构指针 - * @return N706A_HTTP_SUCCESS成功,其他失败 - */ -n706a_httpc_result_t n706a_http_client_post(n706a_http_client_t *client, - const char *path, - const char *content_type, - const char *body, - size_t body_len, - n706a_http_client_data_t *client_data); - -/** - * @brief 发送HTTP PUT请求 - * @param client HTTP客户端结构指针 - * @param path 请求路径 - * @param content_type 内容类型 - * @param body 请求体数据 - * @param body_len 请求体长度 - * @param client_data 响应数据结构指针 - * @return N706A_HTTP_SUCCESS成功,其他失败 - */ -n706a_httpc_result_t n706a_http_client_put(n706a_http_client_t *client, - const char *path, - const char *content_type, - const char *body, - size_t body_len, - n706a_http_client_data_t *client_data); - -/** - * @brief 发送HTTP DELETE请求 - * @param client HTTP客户端结构指针 - * @param path 请求路径 - * @param client_data 响应数据结构指针 - * @return N706A_HTTP_SUCCESS成功,其他失败 - */ -n706a_httpc_result_t n706a_http_client_delete(n706a_http_client_t *client, - const char *path, - n706a_http_client_data_t *client_data); - -/** - * @brief 发送HTTP HEAD请求 - * @param client HTTP客户端结构指针 - * @param path 请求路径 - * @param client_data 响应数据结构指针 - * @return N706A_HTTP_SUCCESS成功,其他失败 - */ -n706a_httpc_result_t n706a_http_client_head(n706a_http_client_t *client, - const char *path, - n706a_http_client_data_t *client_data); - -/** - * @brief 设置自定义HTTP请求头 - * @param client HTTP客户端结构指针 - * @param header 自定义请求头(如"X-API-Key: abc123\r\n") - * @return N706A_HTTP_SUCCESS成功,其他失败 - */ -n706a_httpc_result_t n706a_http_client_set_header(n706a_http_client_t *client, - const char *header); - -/** - * @brief 设置HTTP基本认证 - * @param client HTTP客户端结构指针 - * @param username 用户名 - * @param password 密码 - * @return N706A_HTTP_SUCCESS成功,其他失败 - */ -n706a_httpc_result_t n706a_http_client_set_auth(n706a_http_client_t *client, - const char *username, - const char *password); - -/** - * @brief 设置Keep-Alive模式 - * @param client HTTP客户端结构指针 - * @param keepalive true启用长连接,false关闭 - * @return N706A_HTTP_SUCCESS成功,其他失败 - */ -n706a_httpc_result_t n706a_http_client_set_keepalive(n706a_http_client_t *client, - bool keepalive); - -/** - * @brief 关闭HTTP连接 - * @param client HTTP客户端结构指针 - * @return N706A_HTTP_SUCCESS成功,其他失败 - */ -n706a_httpc_result_t n706a_http_client_close(n706a_http_client_t *client); - -/** - * @brief 检查HTTP连接状态 - * @param client HTTP客户端结构指针 - * @return true已连接,false未连接 - */ -bool n706a_http_client_is_connected(n706a_http_client_t *client); - -/** - * @brief 获取最后的HTTP响应码 - * @param client HTTP客户端结构指针 - * @return HTTP响应码(如200, 404等) - */ -int n706a_http_client_get_response_code(n706a_http_client_t *client); - -/* ========== 辅助函数 ========== */ - -/** - * @brief 解析完整URL并发起GET请求(便捷函数) - * @param client HTTP客户端结构指针 - * @param url 完整URL(如"http://api.example.com/data"或"https://secure.com/") - * @param client_data 响应数据结构指针 - * @return N706A_HTTP_SUCCESS成功,其他失败 - * @note 此函数会自动解析URL、建立连接并发送GET请求 - */ -n706a_httpc_result_t n706a_http_client_get_url(n706a_http_client_t *client, - const char *url, - n706a_http_client_data_t *client_data); - -/** - * @brief 解析完整URL并发起POST请求(便捷函数) - * @param client HTTP客户端结构指针 - * @param url 完整URL - * @param content_type 内容类型 - * @param body 请求体数据 - * @param body_len 请求体长度 - * @param client_data 响应数据结构指针 - * @return N706A_HTTP_SUCCESS成功,其他失败 - */ -n706a_httpc_result_t n706a_http_client_post_url(n706a_http_client_t *client, - const char *url, - const char *content_type, - const char *body, - size_t body_len, - n706a_http_client_data_t *client_data); - -#ifdef __cplusplus -} -#endif - -#endif // N706A_HTTP_CLIENT_H - diff --git a/code/sdk/zhihui/ppp/include/n706a_tcp_client.h b/code/sdk/zhihui/ppp/include/n706a_tcp_client.h deleted file mode 100644 index db6a065c9..000000000 --- a/code/sdk/zhihui/ppp/include/n706a_tcp_client.h +++ /dev/null @@ -1,200 +0,0 @@ -/* - * Copyright (c) Huawei Technologies Co., Ltd. 2025-2025. All rights reserved. - * - * N706A TCP客户端接口 - * 基于N706A AT指令实现TCP socket功能 - * - * SSL实现方式: - * - N706A_SSL_USE_AT_COMMAND: 使用AT+SSLTCP*指令(默认,推荐) - * - N706A_SSL_USE_MBEDTLS: 使用mbedtls库(需要额外库支持) - */ - -#ifndef N706A_TCP_CLIENT_H -#define N706A_TCP_CLIENT_H - -#include -#include -#include - -/* ========== SSL实现方式选择 ========== */ -/** - * 编译时选择SSL实现方式: - * gcc -DN706A_SSL_USE_AT_COMMAND ... (使用AT指令,默认) - * gcc -DN706A_SSL_USE_MBEDTLS ... (使用mbedtls) - */ -#if !defined(N706A_SSL_USE_AT_COMMAND) && !defined(N706A_SSL_USE_MBEDTLS) - /* 默认使用AT指令实现 */ - #define N706A_SSL_USE_AT_COMMAND 1 -#endif - -#if defined(N706A_SSL_USE_AT_COMMAND) && defined(N706A_SSL_USE_MBEDTLS) - #error "Cannot define both N706A_SSL_USE_AT_COMMAND and N706A_SSL_USE_MBEDTLS" -#endif - -/* 如果使用mbedtls,包含相关头文件 */ -#ifdef N706A_SSL_USE_MBEDTLS - #include "mbedtls/net_sockets.h" - #include "mbedtls/ssl.h" - #include "mbedtls/entropy.h" - #include "mbedtls/ctr_drbg.h" - #include "mbedtls/x509.h" - #include "mbedtls/error.h" -#endif - -#ifdef __cplusplus -extern "C" { -#endif - -/* N706A TCP错误码 */ -typedef enum { - N706A_TCP_SUCCESS = 0, /**< 操作成功 */ - N706A_TCP_ERROR = -1, /**< 通用错误 */ - N706A_TCP_EINVAL = -2, /**< 参数无效 */ - N706A_TCP_ENOBUFS = -3, /**< 缓冲区不足 */ - N706A_TCP_ENOTCONN = -4, /**< 未连接 */ - N706A_TCP_ECONNREF = -5, /**< 连接被拒绝 */ - N706A_TCP_ETIMEDOUT= -6, /**< 超时 */ - N706A_TCP_EINPROGRESS = -7, /**< 操作进行中 */ -} n706a_tcp_result_t; - -/* TCP socket状态 */ -typedef enum { - N706A_TCP_STATE_CLOSED = 0, /**< 关闭 */ - N706A_TCP_STATE_CONNECTING, /**< 连接中 */ - N706A_TCP_STATE_CONNECTED, /**< 已连接 */ - N706A_TCP_STATE_ERROR /**< 错误状态 */ -} n706a_tcp_state_t; - -/* TCP socket结构 */ -typedef struct { - int socket_id; /**< socket编号(0-5) */ - n706a_tcp_state_t state; /**< 连接状态 */ - char remote_ip[64]; /**< 远程IP或域名 */ - uint16_t remote_port; /**< 远程端口 */ - bool is_ssl; /**< 是否SSL连接 */ - uint32_t timeout_ms; /**< 超时时间 */ - - /* SSL相关字段(根据编译宏不同而不同) */ -#ifdef N706A_SSL_USE_AT_COMMAND - /* AT指令模式:SSL由模组处理 */ - int ssl_version; /**< SSL版本(0-5) */ - int auth_mode; /**< 认证模式(0-2) */ - char ca_cert_file[64]; /**< CA证书文件名 */ -#endif - -#ifdef N706A_SSL_USE_MBEDTLS - /* mbedtls模式:需要SSL上下文 */ - mbedtls_ssl_context ssl; /**< SSL上下文 */ - mbedtls_ssl_config conf; /**< SSL配置 */ - mbedtls_entropy_context entropy; /**< 熵源 */ - mbedtls_ctr_drbg_context ctr_drbg; /**< 随机数生成器 */ - mbedtls_x509_crt cacert; /**< CA证书 */ - mbedtls_x509_crt clicert; /**< 客户端证书(可选) */ - mbedtls_pk_context pkey; /**< 客户端私钥(可选) */ - bool ssl_initialized; /**< SSL是否已初始化 */ -#endif -} n706a_tcp_socket_t; - -/** - * @brief 创建TCP socket - * @param sock TCP socket指针 - * @param socket_id socket编号(0-5) - * @return N706A_TCP_SUCCESS成功,其他值表示失败 - */ -n706a_tcp_result_t n706a_tcp_socket_create(n706a_tcp_socket_t *sock, int socket_id); - -/** - * @brief 连接到TCP服务器 - * @param sock TCP socket指针 - * @param host 主机地址(IP或域名) - * @param port 端口号 - * @param use_ssl 是否使用SSL连接(用于WSS) - * @param timeout_ms 超时时间(毫秒) - * @return N706A_TCP_SUCCESS成功,其他值表示失败 - * @note 使用SSL时会调用AT+SSLTCPSETUP,否则调用AT+TCPSETUP - */ -n706a_tcp_result_t n706a_tcp_connect(n706a_tcp_socket_t *sock, const char *host, - uint16_t port, bool use_ssl, uint32_t timeout_ms); - -/** - * @brief 配置SSL参数(在SSL连接前调用) - * @param sock TCP socket指针 - * @param ssl_version SSL版本 - * AT指令模式:0-5 (0=SSL3.0, 1=TLS1.0, 2=TLS1.1, 3=TLS1.2, 4=TLS1.0/1.1/1.2, 5=TLS1.3) - * mbedtls模式:使用MBEDTLS_SSL_MINOR_VERSION_*常量 - * @param auth_mode 认证模式 - * AT指令模式:0=不认证, 1=服务器认证, 2=双向认证 - * mbedtls模式:MBEDTLS_SSL_VERIFY_NONE/OPTIONAL/REQUIRED - * @param ca_cert CA证书 - * AT指令模式:证书文件名(如"ca.crt") - * mbedtls模式:证书内容(PEM格式字符串) - * @return N706A_TCP_SUCCESS成功,其他值表示失败 - */ -n706a_tcp_result_t n706a_tcp_config_ssl(n706a_tcp_socket_t *sock, - int ssl_version, - int auth_mode, - const char *ca_cert); - -#ifdef N706A_SSL_USE_MBEDTLS -/** - * @brief 配置客户端证书和私钥(mbedtls模式,双向认证) - * @param sock TCP socket指针 - * @param client_cert 客户端证书(PEM格式) - * @param client_key 客户端私钥(PEM格式) - * @return N706A_TCP_SUCCESS成功,其他值表示失败 - */ -n706a_tcp_result_t n706a_tcp_config_ssl_client_cert(n706a_tcp_socket_t *sock, - const char *client_cert, - const char *client_key); -#endif - -/** - * @brief 发送TCP数据 - * @param sock TCP socket指针 - * @param data 要发送的数据 - * @param len 数据长度 - * @param sent 实际发送的字节数(输出) - * @return N706A_TCP_SUCCESS成功,其他值表示失败 - */ -n706a_tcp_result_t n706a_tcp_send(n706a_tcp_socket_t *sock, const uint8_t *data, - size_t len, size_t *sent); - -/** - * @brief 接收TCP数据 - * @param sock TCP socket指针 - * @param buffer 接收缓冲区 - * @param len 缓冲区大小 - * @param received 实际接收的字节数(输出) - * @param timeout_ms 超时时间(毫秒) - * @return N706A_TCP_SUCCESS成功,其他值表示失败 - */ -n706a_tcp_result_t n706a_tcp_recv(n706a_tcp_socket_t *sock, uint8_t *buffer, - size_t len, size_t *received, uint32_t timeout_ms); - -/** - * @brief 关闭TCP连接 - * @param sock TCP socket指针 - * @return N706A_TCP_SUCCESS成功,其他值表示失败 - */ -n706a_tcp_result_t n706a_tcp_close(n706a_tcp_socket_t *sock); - -/** - * @brief 检查socket是否已连接 - * @param sock TCP socket指针 - * @return true已连接,false未连接 - */ -bool n706a_tcp_is_connected(const n706a_tcp_socket_t *sock); - -/** - * @brief 获取socket状态 - * @param sock TCP socket指针 - * @return socket状态 - */ -n706a_tcp_state_t n706a_tcp_get_state(const n706a_tcp_socket_t *sock); - -#ifdef __cplusplus -} -#endif - -#endif /* N706A_TCP_CLIENT_H */ - diff --git a/code/sdk/zhihui/ppp/include/n706a_websocket.h b/code/sdk/zhihui/ppp/include/n706a_websocket.h deleted file mode 100644 index b7eb62ad4..000000000 --- a/code/sdk/zhihui/ppp/include/n706a_websocket.h +++ /dev/null @@ -1,310 +0,0 @@ -/** - * @file n706a_websocket.h - * @brief N706A WebSocket客户端接口定义 - * - * 基于TCP/SSL TCP实现完整的WebSocket协议(RFC 6455),支持: - * - ws://协议(普通TCP) - * - wss://协议(SSL TCP) - * - 文本和二进制消息 - * - 自动握手和帧封装 - * - 事件回调机制 - * - * @version 1.0 - * @date 2025-10-30 - */ - -#ifndef N706A_WEBSOCKET_H -#define N706A_WEBSOCKET_H - -#include -#include -#include -#include "n706a_tcp_client.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/* WebSocket最大客户端数量 */ -#define N706A_WS_MAX_CLIENTS 6 - -/* WebSocket URL最大长度 */ -#define N706A_WS_MAX_URL_LEN 512 - -/* WebSocket帧操作码 */ -typedef enum { - N706A_WS_OPCODE_CONTINUATION = 0x0, /**< 继续帧 */ - N706A_WS_OPCODE_TEXT = 0x1, /**< 文本帧 */ - N706A_WS_OPCODE_BINARY = 0x2, /**< 二进制帧 */ - N706A_WS_OPCODE_CLOSE = 0x8, /**< 关闭帧 */ - N706A_WS_OPCODE_PING = 0x9, /**< Ping帧 */ - N706A_WS_OPCODE_PONG = 0xA /**< Pong帧 */ -} n706a_ws_opcode_t; - -/* WebSocket状态 */ -typedef enum { - N706A_WS_STATE_CLOSED = 0, /**< 已关闭 */ - N706A_WS_STATE_CONNECTING, /**< 连接中 */ - N706A_WS_STATE_OPEN, /**< 已打开 */ - N706A_WS_STATE_CLOSING /**< 关闭中 */ -} n706a_ws_state_t; - -/* WebSocket关闭状态码 */ -typedef enum { - N706A_WS_CLOSE_NORMAL = 1000, /**< 正常关闭 */ - N706A_WS_CLOSE_GOING_AWAY = 1001, /**< 端点离开 */ - N706A_WS_CLOSE_PROTOCOL_ERROR = 1002, /**< 协议错误 */ - N706A_WS_CLOSE_UNSUPPORTED = 1003, /**< 不支持的数据 */ - N706A_WS_CLOSE_NO_STATUS = 1005, /**< 无状态码 */ - N706A_WS_CLOSE_ABNORMAL = 1006, /**< 异常关闭 */ - N706A_WS_CLOSE_INVALID_DATA = 1007, /**< 无效数据 */ - N706A_WS_CLOSE_POLICY = 1008, /**< 策略违反 */ - N706A_WS_CLOSE_TOO_LARGE = 1009, /**< 消息过大 */ - N706A_WS_CLOSE_EXTENSION = 1010, /**< 扩展协商失败 */ - N706A_WS_CLOSE_SERVER_ERROR = 1011 /**< 服务器错误 */ -} n706a_ws_close_code_t; - -/* WebSocket错误码 */ -typedef enum { - N706A_WS_SUCCESS = 0, /**< 成功 */ - N706A_WS_ERROR_INVALID_PARAM = -1, /**< 参数无效 */ - N706A_WS_ERROR_NO_MEMORY = -2, /**< 内存不足 */ - N706A_WS_ERROR_NOT_CONNECTED = -3, /**< 未连接 */ - N706A_WS_ERROR_ALREADY_OPEN = -4, /**< 已经打开 */ - N706A_WS_ERROR_HANDSHAKE = -5, /**< 握手失败 */ - N706A_WS_ERROR_SEND_FAIL = -6, /**< 发送失败 */ - N706A_WS_ERROR_RECV_FAIL = -7, /**< 接收失败 */ - N706A_WS_ERROR_PROTOCOL = -8, /**< 协议错误 */ - N706A_WS_ERROR_TIMEOUT = -9, /**< 超时 */ - N706A_WS_ERROR_CLOSED = -10 /**< 连接已关闭 */ -} n706a_ws_result_t; - -/* 前置声明 */ -struct n706a_websocket_client; - -/** - * @brief WebSocket连接打开回调 - * @param client_id 客户端ID - */ -typedef void (*n706a_ws_on_open_callback_t)(int client_id); - -/** - * @brief WebSocket消息接收回调 - * @param client_id 客户端ID - * @param data 消息数据 - * @param length 数据长度 - * @param is_finished 是否是最后一帧 - * @param is_binary 是否是二进制数据 - */ -typedef void (*n706a_ws_on_message_callback_t)(int client_id, - const char *data, - size_t length, - bool is_finished, - bool is_binary); - -/** - * @brief WebSocket错误回调 - * @param client_id 客户端ID - * @param error_code 错误码 - * @param error_msg 错误消息 - */ -typedef void (*n706a_ws_on_error_callback_t)(int client_id, - int error_code, - const char *error_msg); - -/** - * @brief WebSocket连接关闭回调 - * @param client_id 客户端ID - * @param close_code 关闭状态码 - * @param reason 关闭原因 - */ -typedef void (*n706a_ws_on_close_callback_t)(int client_id, - int close_code, - const char *reason); - -/** - * @brief N706A WebSocket客户端结构 - */ -typedef struct n706a_websocket_client { - int client_id; /**< 客户端ID (0-5) */ - n706a_tcp_socket_t tcp_sock; /**< 底层TCP socket */ - n706a_ws_state_t state; /**< WebSocket状态 */ - bool is_ssl; /**< 是否WSS连接 */ - - /* URL信息 */ - char url[N706A_WS_MAX_URL_LEN]; /**< 完整URL */ - char host[128]; /**< 主机名 */ - uint16_t port; /**< 端口号 */ - char path[256]; /**< 路径 */ - - /* WebSocket协议 */ - char sec_key[32]; /**< Sec-WebSocket-Key */ - char sec_accept[32]; /**< Sec-WebSocket-Accept */ - - /* 接收缓冲区 */ - uint8_t *recv_buffer; /**< 接收缓冲区 */ - size_t recv_buffer_size; /**< 缓冲区大小 */ - size_t recv_buffer_len; /**< 已接收数据长度 */ - - /* 回调函数 */ - n706a_ws_on_open_callback_t on_open; - n706a_ws_on_message_callback_t on_message; - n706a_ws_on_error_callback_t on_error; - n706a_ws_on_close_callback_t on_close; - - /* 运行控制 */ - bool running; /**< 接收线程运行标志 */ - void *recv_thread; /**< 接收线程句柄 */ - -} n706a_websocket_client_t; - -/** - * @brief 创建WebSocket客户端 - * @param on_open 连接打开回调 - * @param on_message 消息接收回调 - * @param on_error 错误回调 - * @param on_close 连接关闭回调 - * @return 客户端ID (0-5),失败返回-1 - */ -int n706a_websocket_constructor(n706a_ws_on_open_callback_t on_open, - n706a_ws_on_message_callback_t on_message, - n706a_ws_on_error_callback_t on_error, - n706a_ws_on_close_callback_t on_close); - -/** - * @brief 创建WebSocket客户端并指定socket ID - * @param socket_id TCP socket ID (0-5) - * @param on_open 连接打开回调 - * @param on_message 消息接收回调 - * @param on_error 错误回调 - * @param on_close 连接关闭回调 - * @return 客户端ID,失败返回-1 - */ -int n706a_websocket_constructor_with_socket(int socket_id, - n706a_ws_on_open_callback_t on_open, - n706a_ws_on_message_callback_t on_message, - n706a_ws_on_error_callback_t on_error, - n706a_ws_on_close_callback_t on_close); - -/** - * @brief 连接到WebSocket服务器 - * @param client_id 客户端ID - * @param url WebSocket URL (ws://或wss://) - * @param option 可选参数(如子协议等),可为NULL - * @return N706A_WS_SUCCESS成功,其他失败 - */ -int n706a_websocket_connect(int client_id, const char *url, const char *option); - -/** - * @brief 发送文本消息 - * @param client_id 客户端ID - * @param data 文本数据 - * @param length 数据长度 - * @return N706A_WS_SUCCESS成功,其他失败 - */ -int n706a_websocket_send_text(int client_id, const char *data, size_t length); - -/** - * @brief 发送二进制消息 - * @param client_id 客户端ID - * @param data 二进制数据 - * @param length 数据长度 - * @return N706A_WS_SUCCESS成功,其他失败 - */ -int n706a_websocket_send_binary(int client_id, const uint8_t *data, size_t length); - -/** - * @brief 发送Ping帧 - * @param client_id 客户端ID - * @param data Ping数据(可为NULL) - * @param length 数据长度 - * @return N706A_WS_SUCCESS成功,其他失败 - */ -int n706a_websocket_send_ping(int client_id, const char *data, size_t length); - -/** - * @brief 发送Pong帧 - * @param client_id 客户端ID - * @param data Pong数据(可为NULL) - * @param length 数据长度 - * @return N706A_WS_SUCCESS成功,其他失败 - */ -int n706a_websocket_send_pong(int client_id, const char *data, size_t length); - -/** - * @brief 关闭WebSocket连接 - * @param client_id 客户端ID - * @return N706A_WS_SUCCESS成功,其他失败 - */ -int n706a_websocket_close(int client_id); - -/** - * @brief 关闭WebSocket连接(带状态码和原因) - * @param client_id 客户端ID - * @param close_code 关闭状态码 - * @param reason 关闭原因(可为NULL) - * @return N706A_WS_SUCCESS成功,其他失败 - */ -int n706a_websocket_close_with_reason(int client_id, - n706a_ws_close_code_t close_code, - const char *reason); - -/** - * @brief 获取WebSocket连接状态 - * @param client_id 客户端ID - * @return WebSocket状态 - */ -n706a_ws_state_t n706a_websocket_get_state(int client_id); - -/** - * @brief 检查WebSocket是否已连接 - * @param client_id 客户端ID - * @return true已连接,false未连接 - */ -bool n706a_websocket_is_connected(int client_id); - -/** - * @brief 销毁WebSocket客户端 - * @param client_id 客户端ID - * @return N706A_WS_SUCCESS成功,其他失败 - */ -int n706a_websocket_destructor(int client_id); - -/* ========== 内部辅助函数(高级用户可选使用) ========== */ - -/** - * @brief 解析WebSocket URL - * @param url 完整URL - * @param protocol 协议(输出,"ws"或"wss") - * @param host 主机名(输出) - * @param port 端口号(输出) - * @param path 路径(输出) - * @return N706A_WS_SUCCESS成功,其他失败 - */ -int n706a_websocket_parse_url(const char *url, - char *protocol, - char *host, - uint16_t *port, - char *path); - -/** - * @brief 生成WebSocket Sec-WebSocket-Key - * @param key 输出缓冲区(至少25字节) - * @return N706A_WS_SUCCESS成功,其他失败 - */ -int n706a_websocket_generate_key(char *key); - -/** - * @brief 计算WebSocket Sec-WebSocket-Accept - * @param key Sec-WebSocket-Key - * @param accept 输出缓冲区(至少29字节) - * @return N706A_WS_SUCCESS成功,其他失败 - */ -int n706a_websocket_calculate_accept(const char *key, char *accept); - -#ifdef __cplusplus -} -#endif - -#endif /* N706A_WEBSOCKET_H */ - diff --git a/code/sdk/zhihui/ppp/src/n706a_http_client.c b/code/sdk/zhihui/ppp/src/n706a_http_client.c deleted file mode 100644 index 649e370af..000000000 --- a/code/sdk/zhihui/ppp/src/n706a_http_client.c +++ /dev/null @@ -1,785 +0,0 @@ -/** - * @file n706a_http_client.c - * @brief N706A HTTP/HTTPS客户端实现(基于TCP) - * - * 基于TCP手动构造HTTP协议,支持: - * - HTTP (普通TCP) - * - HTTPS (SSL TCP) - * - 6个并发连接 - * - 所有标准HTTP方法 - */ - -#include "n706a_http_client.h" -#include -#include -#include - -/* Base64编码表(用于Basic Auth) */ -static const char base64_table[] = - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; - -/* ========== 私有函数声明 ========== */ - -static int http_build_request(n706a_http_client_t *client, - const char *method, - const char *path, - const char *body, - size_t body_len, - const char *content_type, - char *request_buf, - size_t buf_size); - -static n706a_httpc_result_t http_parse_response(const char *response, - size_t response_len, - n706a_http_client_t *client, - n706a_http_client_data_t *data); - -static void http_base64_encode(const char *input, size_t len, char *output); - -static int http_parse_url(const char *url, - char *protocol, - char *host, - uint16_t *port, - char *path); - -/* ========== 公共函数实现 ========== */ - -/** - * @brief 初始化HTTP客户端 - */ -n706a_httpc_result_t n706a_http_client_init(n706a_http_client_t *client, int socket_id) -{ - if (!client || socket_id < 0 || socket_id > 5) { - return N706A_HTTP_EARG; - } - - memset(client, 0, sizeof(n706a_http_client_t)); - - client->socket_id = socket_id; - client->is_https = false; - client->remote_port = 0; - client->response_code = 0; - client->connected = false; - client->keepalive = false; - client->timeout_ms = 10000; // 默认10秒 - - /* 初始化TCP socket */ - n706a_tcp_socket_create(&client->tcp_sock, socket_id); - - return N706A_HTTP_SUCCESS; -} - -/** - * @brief 连接到HTTP/HTTPS服务器 - */ -n706a_httpc_result_t n706a_http_client_connect(n706a_http_client_t *client, - const char *host, - uint16_t port, - bool use_ssl) -{ - if (!client || !host) { - return N706A_HTTP_EARG; - } - - /* 保存连接信息 */ - strncpy(client->host, host, sizeof(client->host) - 1); - client->remote_port = port; - client->is_https = use_ssl; - - /* 建立TCP连接 */ - n706a_tcp_result_t ret = n706a_tcp_connect(&client->tcp_sock, - host, - port, - use_ssl, - 30000); // SSL握手需要更长时间 - - if (ret == N706A_TCP_SUCCESS) { - client->connected = true; - return N706A_HTTP_SUCCESS; - } else if (ret == N706A_TCP_ETIMEDOUT) { - return N706A_HTTP_ETIMEOUT; - } else { - return N706A_HTTP_ECONN; - } -} - -/** - * @brief 发送HTTP GET请求 - */ -n706a_httpc_result_t n706a_http_client_get(n706a_http_client_t *client, - const char *path, - n706a_http_client_data_t *client_data) -{ - if (!client || !path || !client_data) { - return N706A_HTTP_EARG; - } - - if (!client->connected) { - return N706A_HTTP_ECONN; - } - - /* 构造GET请求 */ - char request_buf[2048]; - int request_len = http_build_request(client, "GET", path, - NULL, 0, NULL, - request_buf, sizeof(request_buf)); - - if (request_len < 0) { - return N706A_HTTP_ENOBUFS; - } - - /* 发送请求 */ - size_t sent; - n706a_tcp_result_t ret = n706a_tcp_send(&client->tcp_sock, - (uint8_t*)request_buf, - request_len, - &sent); - - if (ret != N706A_TCP_SUCCESS || sent != (size_t)request_len) { - return N706A_HTTP_ESEND; - } - - /* 接收响应 */ - uint8_t response_buf[8192]; - size_t received; - - ret = n706a_tcp_recv(&client->tcp_sock, - response_buf, - sizeof(response_buf) - 1, - &received, - client->timeout_ms); - - if (ret != N706A_TCP_SUCCESS) { - return N706A_HTTP_ERECV; - } - - response_buf[received] = '\0'; - - /* 解析响应 */ - return http_parse_response((char*)response_buf, received, client, client_data); -} - -/** - * @brief 发送HTTP POST请求 - */ -n706a_httpc_result_t n706a_http_client_post(n706a_http_client_t *client, - const char *path, - const char *content_type, - const char *body, - size_t body_len, - n706a_http_client_data_t *client_data) -{ - if (!client || !path || !client_data) { - return N706A_HTTP_EARG; - } - - if (!client->connected) { - return N706A_HTTP_ECONN; - } - - /* 构造POST请求 */ - char request_buf[4096]; - int request_len = http_build_request(client, "POST", path, - body, body_len, content_type, - request_buf, sizeof(request_buf)); - - if (request_len < 0) { - return N706A_HTTP_ENOBUFS; - } - - /* 发送请求 */ - size_t sent; - n706a_tcp_result_t ret = n706a_tcp_send(&client->tcp_sock, - (uint8_t*)request_buf, - request_len, - &sent); - - if (ret != N706A_TCP_SUCCESS) { - return N706A_HTTP_ESEND; - } - - /* 接收响应 */ - uint8_t response_buf[8192]; - size_t received; - - ret = n706a_tcp_recv(&client->tcp_sock, - response_buf, - sizeof(response_buf) - 1, - &received, - client->timeout_ms); - - if (ret != N706A_TCP_SUCCESS) { - return N706A_HTTP_ERECV; - } - - response_buf[received] = '\0'; - - return http_parse_response((char*)response_buf, received, client, client_data); -} - -/** - * @brief 发送HTTP PUT请求 - */ -n706a_httpc_result_t n706a_http_client_put(n706a_http_client_t *client, - const char *path, - const char *content_type, - const char *body, - size_t body_len, - n706a_http_client_data_t *client_data) -{ - if (!client || !path || !client_data) { - return N706A_HTTP_EARG; - } - - if (!client->connected) { - return N706A_HTTP_ECONN; - } - - char request_buf[4096]; - int request_len = http_build_request(client, "PUT", path, - body, body_len, content_type, - request_buf, sizeof(request_buf)); - - if (request_len < 0) { - return N706A_HTTP_ENOBUFS; - } - - size_t sent; - n706a_tcp_send(&client->tcp_sock, (uint8_t*)request_buf, request_len, &sent); - - uint8_t response_buf[8192]; - size_t received; - n706a_tcp_recv(&client->tcp_sock, response_buf, sizeof(response_buf) - 1, - &received, client->timeout_ms); - - response_buf[received] = '\0'; - - return http_parse_response((char*)response_buf, received, client, client_data); -} - -/** - * @brief 发送HTTP DELETE请求 - */ -n706a_httpc_result_t n706a_http_client_delete(n706a_http_client_t *client, - const char *path, - n706a_http_client_data_t *client_data) -{ - if (!client || !path || !client_data) { - return N706A_HTTP_EARG; - } - - if (!client->connected) { - return N706A_HTTP_ECONN; - } - - char request_buf[2048]; - int request_len = http_build_request(client, "DELETE", path, - NULL, 0, NULL, - request_buf, sizeof(request_buf)); - - if (request_len < 0) { - return N706A_HTTP_ENOBUFS; - } - - size_t sent; - n706a_tcp_send(&client->tcp_sock, (uint8_t*)request_buf, request_len, &sent); - - uint8_t response_buf[8192]; - size_t received; - n706a_tcp_recv(&client->tcp_sock, response_buf, sizeof(response_buf) - 1, - &received, client->timeout_ms); - - response_buf[received] = '\0'; - - return http_parse_response((char*)response_buf, received, client, client_data); -} - -/** - * @brief 发送HTTP HEAD请求 - */ -n706a_httpc_result_t n706a_http_client_head(n706a_http_client_t *client, - const char *path, - n706a_http_client_data_t *client_data) -{ - if (!client || !path || !client_data) { - return N706A_HTTP_EARG; - } - - if (!client->connected) { - return N706A_HTTP_ECONN; - } - - char request_buf[2048]; - int request_len = http_build_request(client, "HEAD", path, - NULL, 0, NULL, - request_buf, sizeof(request_buf)); - - if (request_len < 0) { - return N706A_HTTP_ENOBUFS; - } - - size_t sent; - n706a_tcp_send(&client->tcp_sock, (uint8_t*)request_buf, request_len, &sent); - - uint8_t response_buf[8192]; - size_t received; - n706a_tcp_recv(&client->tcp_sock, response_buf, sizeof(response_buf) - 1, - &received, client->timeout_ms); - - response_buf[received] = '\0'; - - return http_parse_response((char*)response_buf, received, client, client_data); -} - -/** - * @brief 设置自定义HTTP请求头 - */ -n706a_httpc_result_t n706a_http_client_set_header(n706a_http_client_t *client, - const char *header) -{ - if (!client) { - return N706A_HTTP_EARG; - } - - if (client->header) { - free(client->header); - } - - if (header) { - client->header = strdup(header); - } else { - client->header = NULL; - } - - return N706A_HTTP_SUCCESS; -} - -/** - * @brief 设置HTTP基本认证 - */ -n706a_httpc_result_t n706a_http_client_set_auth(n706a_http_client_t *client, - const char *username, - const char *password) -{ - if (!client) { - return N706A_HTTP_EARG; - } - - if (client->auth_user) { - free(client->auth_user); - } - if (client->auth_password) { - free(client->auth_password); - } - - if (username) { - client->auth_user = strdup(username); - } else { - client->auth_user = NULL; - } - - if (password) { - client->auth_password = strdup(password); - } else { - client->auth_password = NULL; - } - - return N706A_HTTP_SUCCESS; -} - -/** - * @brief 设置Keep-Alive模式 - */ -n706a_httpc_result_t n706a_http_client_set_keepalive(n706a_http_client_t *client, - bool keepalive) -{ - if (!client) { - return N706A_HTTP_EARG; - } - - client->keepalive = keepalive; - - return N706A_HTTP_SUCCESS; -} - -/** - * @brief 关闭HTTP连接 - */ -n706a_httpc_result_t n706a_http_client_close(n706a_http_client_t *client) -{ - if (!client) { - return N706A_HTTP_EARG; - } - - if (client->connected) { - n706a_tcp_close(&client->tcp_sock); - client->connected = false; - } - - if (client->header) { - free(client->header); - client->header = NULL; - } - - if (client->auth_user) { - free(client->auth_user); - client->auth_user = NULL; - } - - if (client->auth_password) { - free(client->auth_password); - client->auth_password = NULL; - } - - return N706A_HTTP_SUCCESS; -} - -/** - * @brief 检查HTTP连接状态 - */ -bool n706a_http_client_is_connected(n706a_http_client_t *client) -{ - if (!client) { - return false; - } - - return client->connected && n706a_tcp_is_connected(&client->tcp_sock); -} - -/** - * @brief 获取最后的HTTP响应码 - */ -int n706a_http_client_get_response_code(n706a_http_client_t *client) -{ - if (!client) { - return 0; - } - - return client->response_code; -} - -/** - * @brief 解析完整URL并发起GET请求 - */ -n706a_httpc_result_t n706a_http_client_get_url(n706a_http_client_t *client, - const char *url, - n706a_http_client_data_t *client_data) -{ - if (!client || !url || !client_data) { - return N706A_HTTP_EARG; - } - - char protocol[8]; - char host[256]; - char path[512]; - uint16_t port; - - if (http_parse_url(url, protocol, host, &port, path) != 0) { - return N706A_HTTP_EPARSE; - } - - bool use_ssl = (strcmp(protocol, "https") == 0); - - n706a_httpc_result_t ret = n706a_http_client_connect(client, host, port, use_ssl); - if (ret != N706A_HTTP_SUCCESS) { - return ret; - } - - return n706a_http_client_get(client, path, client_data); -} - -/** - * @brief 解析完整URL并发起POST请求 - */ -n706a_httpc_result_t n706a_http_client_post_url(n706a_http_client_t *client, - const char *url, - const char *content_type, - const char *body, - size_t body_len, - n706a_http_client_data_t *client_data) -{ - if (!client || !url || !client_data) { - return N706A_HTTP_EARG; - } - - char protocol[8]; - char host[256]; - char path[512]; - uint16_t port; - - if (http_parse_url(url, protocol, host, &port, path) != 0) { - return N706A_HTTP_EPARSE; - } - - bool use_ssl = (strcmp(protocol, "https") == 0); - - n706a_httpc_result_t ret = n706a_http_client_connect(client, host, port, use_ssl); - if (ret != N706A_HTTP_SUCCESS) { - return ret; - } - - return n706a_http_client_post(client, path, content_type, body, body_len, client_data); -} - -/* ========== 私有函数实现 ========== */ - -/** - * @brief 构造HTTP请求 - */ -static int http_build_request(n706a_http_client_t *client, - const char *method, - const char *path, - const char *body, - size_t body_len, - const char *content_type, - char *request_buf, - size_t buf_size) -{ - int len = 0; - - /* 请求行 */ - len += snprintf(request_buf + len, buf_size - len, - "%s %s HTTP/1.1\r\n", method, path); - - /* Host头 */ - len += snprintf(request_buf + len, buf_size - len, - "Host: %s\r\n", client->host); - - /* User-Agent */ - len += snprintf(request_buf + len, buf_size - len, - "User-Agent: N706A-HTTP-Client/1.0\r\n"); - - /* Accept */ - len += snprintf(request_buf + len, buf_size - len, - "Accept: */*\r\n"); - - /* Connection */ - len += snprintf(request_buf + len, buf_size - len, - "Connection: %s\r\n", - client->keepalive ? "keep-alive" : "close"); - - /* Content-Type和Content-Length(POST/PUT) */ - if (body && body_len > 0) { - if (content_type) { - len += snprintf(request_buf + len, buf_size - len, - "Content-Type: %s\r\n", content_type); - } - - len += snprintf(request_buf + len, buf_size - len, - "Content-Length: %zu\r\n", body_len); - } - - /* Basic认证 */ - if (client->auth_user && client->auth_password) { - char auth_str[256]; - char auth_b64[512]; - - snprintf(auth_str, sizeof(auth_str), "%s:%s", - client->auth_user, client->auth_password); - - http_base64_encode(auth_str, strlen(auth_str), auth_b64); - - len += snprintf(request_buf + len, buf_size - len, - "Authorization: Basic %s\r\n", auth_b64); - } - - /* 自定义请求头 */ - if (client->header) { - len += snprintf(request_buf + len, buf_size - len, - "%s", client->header); - } - - /* 空行 */ - len += snprintf(request_buf + len, buf_size - len, "\r\n"); - - /* 请求体 */ - if (body && body_len > 0) { - if (len + body_len < buf_size) { - memcpy(request_buf + len, body, body_len); - len += body_len; - } else { - return -1; // 缓冲区不足 - } - } - - return len; -} - -/** - * @brief 解析HTTP响应 - */ -static n706a_httpc_result_t http_parse_response(const char *response, - size_t response_len, - n706a_http_client_t *client, - n706a_http_client_data_t *data) -{ - if (!response || !client || !data) { - return N706A_HTTP_EARG; - } - - /* 解析状态行 */ - int major, minor, status_code; - if (sscanf(response, "HTTP/%d.%d %d", &major, &minor, &status_code) != 3) { - return N706A_HTTP_EPROTO; - } - - client->response_code = status_code; - - /* 查找头尾分界 */ - const char *header_end = strstr(response, "\r\n\r\n"); - if (!header_end) { - return N706A_HTTP_EPROTO; - } - - /* 提取响应头 */ - size_t header_len = header_end - response; - if (data->headerBuf && data->headerBufLen > 0) { - size_t copy_len = (header_len < (size_t)data->headerBufLen - 1) ? - header_len : (size_t)data->headerBufLen - 1; - memcpy(data->headerBuf, response, copy_len); - data->headerBuf[copy_len] = '\0'; - } - - /* 提取Content-Length */ - const char *content_len_str = strcasestr(response, "Content-Length:"); - if (content_len_str && content_len_str < header_end) { - data->responseContentLen = atoi(content_len_str + 15); - } else { - data->responseContentLen = 0; - } - - /* 检查是否chunked */ - if (strcasestr(response, "Transfer-Encoding: chunked")) { - data->isChunked = true; - } else { - data->isChunked = false; - } - - /* 提取响应体 */ - const char *body = header_end + 4; - size_t body_len = response_len - (body - response); - - if (data->responseBuf && data->responseBufLen > 0) { - size_t copy_len = (body_len < (size_t)data->responseBufLen - 1) ? - body_len : (size_t)data->responseBufLen - 1; - memcpy(data->responseBuf, body, copy_len); - data->responseBuf[copy_len] = '\0'; - data->contentBlockLen = copy_len; - } - - /* 检查是否还有更多数据 */ - if (data->responseContentLen > 0 && body_len < (size_t)data->responseContentLen) { - data->isMore = true; - } else { - data->isMore = false; - } - - return N706A_HTTP_SUCCESS; -} - -/** - * @brief Base64编码 - */ -static void http_base64_encode(const char *input, size_t len, char *output) -{ - size_t i = 0, j = 0; - unsigned char char_array_3[3]; - unsigned char char_array_4[4]; - - while (len--) { - char_array_3[i++] = *(input++); - if (i == 3) { - char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; - char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); - char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); - char_array_4[3] = char_array_3[2] & 0x3f; - - for (i = 0; i < 4; i++) { - output[j++] = base64_table[char_array_4[i]]; - } - i = 0; - } - } - - if (i) { - for (size_t k = i; k < 3; k++) { - char_array_3[k] = '\0'; - } - - char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; - char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); - char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); - - for (size_t k = 0; k < i + 1; k++) { - output[j++] = base64_table[char_array_4[k]]; - } - - while (i++ < 3) { - output[j++] = '='; - } - } - - output[j] = '\0'; -} - -/** - * @brief 解析URL - */ -static int http_parse_url(const char *url, - char *protocol, - char *host, - uint16_t *port, - char *path) -{ - if (!url || !protocol || !host || !port || !path) { - return -1; - } - - const char *p = url; - - /* 解析协议 */ - if (strncmp(p, "https://", 8) == 0) { - strcpy(protocol, "https"); - *port = 443; - p += 8; - } else if (strncmp(p, "http://", 7) == 0) { - strcpy(protocol, "http"); - *port = 80; - p += 7; - } else { - return -1; - } - - /* 解析host和port */ - const char *path_start = strchr(p, '/'); - const char *port_start = strchr(p, ':'); - - if (port_start && (!path_start || port_start < path_start)) { - /* 有端口号 */ - size_t host_len = port_start - p; - strncpy(host, p, host_len); - host[host_len] = '\0'; - - *port = atoi(port_start + 1); - - if (path_start) { - strcpy(path, path_start); - } else { - strcpy(path, "/"); - } - } else { - /* 没有端口号 */ - if (path_start) { - size_t host_len = path_start - p; - strncpy(host, p, host_len); - host[host_len] = '\0'; - strcpy(path, path_start); - } else { - strcpy(host, p); - strcpy(path, "/"); - } - } - - return 0; -} - diff --git a/code/sdk/zhihui/ppp/src/n706a_tcp_client.c b/code/sdk/zhihui/ppp/src/n706a_tcp_client.c deleted file mode 100644 index d7e068425..000000000 --- a/code/sdk/zhihui/ppp/src/n706a_tcp_client.c +++ /dev/null @@ -1,659 +0,0 @@ -/** - * @file n706a_tcp_client.c - * @brief N706A TCP客户端实现 - * - * 支持两种SSL实现方式: - * - AT指令模式:使用AT+SSLTCP*指令 - * - mbedtls模式:使用mbedtls库 - */ - -#include "n706a_tcp_client.h" -#include "ppp_at_handler.h" -#include "ppp_types.h" -#include -#include -#include - -/* ========== 私有函数声明 ========== */ - -#ifdef N706A_SSL_USE_MBEDTLS -static int tcp_mbedtls_send_callback(void *ctx, const unsigned char *buf, size_t len); -static int tcp_mbedtls_recv_callback(void *ctx, unsigned char *buf, size_t len); -static n706a_tcp_result_t tcp_mbedtls_init_ssl(n706a_tcp_socket_t *sock); -static void tcp_mbedtls_cleanup_ssl(n706a_tcp_socket_t *sock); -#endif - -/* ========== 公共函数实现 ========== */ - -/** - * @brief 创建TCP socket - */ -n706a_tcp_result_t n706a_tcp_socket_create(n706a_tcp_socket_t *sock, int socket_id) -{ - if (!sock || socket_id < 0 || socket_id > 5) { - return N706A_TCP_EINVAL; - } - - memset(sock, 0, sizeof(n706a_tcp_socket_t)); - - sock->socket_id = socket_id; - sock->state = N706A_TCP_STATE_CLOSED; - sock->is_ssl = false; - sock->timeout_ms = 15000; // 默认15秒超时 - -#ifdef N706A_SSL_USE_AT_COMMAND - sock->ssl_version = 3; // 默认TLS 1.2 - sock->auth_mode = 1; // 默认服务器认证 - memset(sock->ca_cert_file, 0, sizeof(sock->ca_cert_file)); -#endif - -#ifdef N706A_SSL_USE_MBEDTLS - sock->ssl_initialized = false; -#endif - - return N706A_TCP_SUCCESS; -} - -/** - * @brief 配置SSL参数 - */ -n706a_tcp_result_t n706a_tcp_config_ssl(n706a_tcp_socket_t *sock, - int ssl_version, - int auth_mode, - const char *ca_cert) -{ - if (!sock) { - return N706A_TCP_EINVAL; - } - -#ifdef N706A_SSL_USE_AT_COMMAND - /* AT指令模式:保存配置参数 */ - sock->ssl_version = ssl_version; - sock->auth_mode = auth_mode; - - if (ca_cert) { - strncpy(sock->ca_cert_file, ca_cert, sizeof(sock->ca_cert_file) - 1); - - /* 配置AT指令 */ - char at_cmd[256]; - - /* 设置SSL版本 */ - snprintf(at_cmd, sizeof(at_cmd), "AT+SSLTCPCFG=\"sslversion\",%d", ssl_version); - ppp_at_response_t response; - ppp_at_send_command(at_cmd, NULL, 5000, &response); - - /* 设置认证模式 */ - snprintf(at_cmd, sizeof(at_cmd), "AT+SSLTCPCFG=\"authmode\",%d", auth_mode); - ppp_at_send_command(at_cmd, NULL, 5000, &response); - - /* 设置CA证书 */ - if (ca_cert && strlen(ca_cert) > 0) { - snprintf(at_cmd, sizeof(at_cmd), "AT+SSLTCPCFG=\"cacert\",\"%s\"", ca_cert); - ppp_at_send_command(at_cmd, NULL, 5000, &response); - } - } -#endif - -#ifdef N706A_SSL_USE_MBEDTLS - /* mbedtls模式:解析CA证书 */ - if (ca_cert && strlen(ca_cert) > 0) { - mbedtls_x509_crt_init(&sock->cacert); - - int ret = mbedtls_x509_crt_parse(&sock->cacert, - (const unsigned char *)ca_cert, - strlen(ca_cert) + 1); - if (ret != 0) { - return N706A_TCP_ERROR; - } - } - - sock->auth_mode = auth_mode; -#endif - - return N706A_TCP_SUCCESS; -} - -#ifdef N706A_SSL_USE_MBEDTLS -/** - * @brief 配置客户端证书和私钥(mbedtls专用) - */ -n706a_tcp_result_t n706a_tcp_config_ssl_client_cert(n706a_tcp_socket_t *sock, - const char *client_cert, - const char *client_key) -{ - if (!sock || !client_cert || !client_key) { - return N706A_TCP_EINVAL; - } - - /* 解析客户端证书 */ - mbedtls_x509_crt_init(&sock->clicert); - int ret = mbedtls_x509_crt_parse(&sock->clicert, - (const unsigned char *)client_cert, - strlen(client_cert) + 1); - if (ret != 0) { - return N706A_TCP_ERROR; - } - - /* 解析私钥 */ - mbedtls_pk_init(&sock->pkey); - ret = mbedtls_pk_parse_key(&sock->pkey, - (const unsigned char *)client_key, - strlen(client_key) + 1, - NULL, 0); - if (ret != 0) { - mbedtls_x509_crt_free(&sock->clicert); - return N706A_TCP_ERROR; - } - - return N706A_TCP_SUCCESS; -} -#endif - -/** - * @brief 连接到TCP服务器 - */ -n706a_tcp_result_t n706a_tcp_connect(n706a_tcp_socket_t *sock, - const char *host, - uint16_t port, - bool use_ssl, - uint32_t timeout_ms) -{ - if (!sock || !host) { - return N706A_TCP_EINVAL; - } - - if (sock->state == N706A_TCP_STATE_CONNECTED) { - return N706A_TCP_SUCCESS; // 已连接 - } - - /* 保存连接信息 */ - strncpy(sock->remote_ip, host, sizeof(sock->remote_ip) - 1); - sock->remote_port = port; - sock->is_ssl = use_ssl; - sock->timeout_ms = timeout_ms; - - char at_cmd[256]; - ppp_at_response_t response; - ppp_error_t ret; - - sock->state = N706A_TCP_STATE_CONNECTING; - -#ifdef N706A_SSL_USE_AT_COMMAND - if (use_ssl) { - /* AT指令SSL模式:使用AT+SSLTCPSETUP */ - snprintf(at_cmd, sizeof(at_cmd), - "AT+SSLTCPSETUP=%d,%s,%d,%d", - sock->socket_id, - host, - port, - sock->auth_mode); - - ret = ppp_at_send_command(at_cmd, NULL, timeout_ms, &response); - - if (ret == PPP_SUCCESS && response.response && - strstr(response.response, "+SSLTCPSETUP:") && - strstr(response.response, ",OK")) { - sock->state = N706A_TCP_STATE_CONNECTED; - return N706A_TCP_SUCCESS; - } - } else { - /* 普通TCP */ - snprintf(at_cmd, sizeof(at_cmd), - "AT+TCPSETUP=%d,%s,%d", - sock->socket_id, - host, - port); - - ret = ppp_at_send_command(at_cmd, NULL, timeout_ms, &response); - - if (ret == PPP_SUCCESS && response.response && - strstr(response.response, "+TCPSETUP:") && - strstr(response.response, ",OK")) { - sock->state = N706A_TCP_STATE_CONNECTED; - return N706A_TCP_SUCCESS; - } - } -#endif - -#ifdef N706A_SSL_USE_MBEDTLS - /* mbedtls模式:先建立TCP,再进行SSL握手 */ - - /* 1. 先建立普通TCP连接 */ - snprintf(at_cmd, sizeof(at_cmd), - "AT+TCPSETUP=%d,%s,%d", - sock->socket_id, - host, - port); - - ret = ppp_at_send_command(at_cmd, NULL, timeout_ms, &response); - - if (ret != PPP_SUCCESS || !response.response || - !strstr(response.response, "+TCPSETUP:") || - !strstr(response.response, ",OK")) { - sock->state = N706A_TCP_STATE_ERROR; - return N706A_TCP_ECONNREF; - } - - /* 2. 如果使用SSL,进行SSL握手 */ - if (use_ssl) { - n706a_tcp_result_t ssl_ret = tcp_mbedtls_init_ssl(sock); - if (ssl_ret != N706A_TCP_SUCCESS) { - n706a_tcp_close(sock); - return ssl_ret; - } - - /* 设置主机名(用于SNI) */ - mbedtls_ssl_set_hostname(&sock->ssl, host); - - /* 执行SSL握手 */ - int handshake_ret; - while ((handshake_ret = mbedtls_ssl_handshake(&sock->ssl)) != 0) { - if (handshake_ret != MBEDTLS_ERR_SSL_WANT_READ && - handshake_ret != MBEDTLS_ERR_SSL_WANT_WRITE) { - /* 握手失败 */ - tcp_mbedtls_cleanup_ssl(sock); - n706a_tcp_close(sock); - sock->state = N706A_TCP_STATE_ERROR; - return N706A_TCP_ERROR; - } - } - - /* 验证证书 */ - uint32_t flags = mbedtls_ssl_get_verify_result(&sock->ssl); - if (flags != 0 && sock->auth_mode != MBEDTLS_SSL_VERIFY_NONE) { - /* 证书验证失败 */ - tcp_mbedtls_cleanup_ssl(sock); - n706a_tcp_close(sock); - sock->state = N706A_TCP_STATE_ERROR; - return N706A_TCP_ERROR; - } - } -#endif - - sock->state = N706A_TCP_STATE_CONNECTED; - return N706A_TCP_SUCCESS; - -error: - sock->state = N706A_TCP_STATE_ERROR; - return N706A_TCP_ECONNREF; -} - -/** - * @brief 发送TCP数据 - */ -n706a_tcp_result_t n706a_tcp_send(n706a_tcp_socket_t *sock, - const uint8_t *data, - size_t len, - size_t *sent) -{ - if (!sock || !data || len == 0 || !sent) { - return N706A_TCP_EINVAL; - } - - if (sock->state != N706A_TCP_STATE_CONNECTED) { - return N706A_TCP_ENOTCONN; - } - -#ifdef N706A_SSL_USE_MBEDTLS - /* mbedtls模式:通过SSL发送 */ - if (sock->is_ssl && sock->ssl_initialized) { - int ret = mbedtls_ssl_write(&sock->ssl, data, len); - if (ret > 0) { - *sent = ret; - return N706A_TCP_SUCCESS; - } else { - return N706A_TCP_ERROR; - } - } -#endif - - /* AT指令模式或普通TCP */ - char at_cmd[64]; - ppp_at_response_t response; - -#ifdef N706A_SSL_USE_AT_COMMAND - if (sock->is_ssl) { - snprintf(at_cmd, sizeof(at_cmd), "AT+SSLTCPSEND=%d,%zu", - sock->socket_id, len); - } else -#endif - { - snprintf(at_cmd, sizeof(at_cmd), "AT+TCPSEND=%d,%zu", - sock->socket_id, len); - } - - /* 发送命令,等待'>'提示符 */ - ppp_error_t ret = ppp_at_send_command(at_cmd, NULL, 5000, &response); - if (ret != PPP_SUCCESS) { - return N706A_TCP_ERROR; - } - - /* 发送实际数据 */ - ret = ppp_at_send_raw_data(data, len, sock->timeout_ms); - if (ret != PPP_SUCCESS) { - return N706A_TCP_ERROR; - } - - *sent = len; - return N706A_TCP_SUCCESS; -} - -/** - * @brief 接收TCP数据 - */ -n706a_tcp_result_t n706a_tcp_recv(n706a_tcp_socket_t *sock, - uint8_t *buffer, - size_t len, - size_t *received, - uint32_t timeout_ms) -{ - if (!sock || !buffer || len == 0 || !received) { - return N706A_TCP_EINVAL; - } - - if (sock->state != N706A_TCP_STATE_CONNECTED) { - return N706A_TCP_ENOTCONN; - } - -#ifdef N706A_SSL_USE_MBEDTLS - /* mbedtls模式:通过SSL接收 */ - if (sock->is_ssl && sock->ssl_initialized) { - mbedtls_ssl_conf_read_timeout(&sock->conf, timeout_ms); - - int ret = mbedtls_ssl_read(&sock->ssl, buffer, len); - if (ret > 0) { - *received = ret; - return N706A_TCP_SUCCESS; - } else if (ret == 0 || ret == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY) { - *received = 0; - return N706A_TCP_SUCCESS; - } else { - return N706A_TCP_ERROR; - } - } -#endif - - /* AT指令模式或普通TCP */ - char at_cmd[64]; - ppp_at_response_t response; - -#ifdef N706A_SSL_USE_AT_COMMAND - if (sock->is_ssl) { - snprintf(at_cmd, sizeof(at_cmd), "AT+SSLTCPREAD=%d,%zu", - sock->socket_id, len); - } else -#endif - { - snprintf(at_cmd, sizeof(at_cmd), "AT+TCPREAD=%d,%zu", - sock->socket_id, len); - } - - ppp_error_t ret = ppp_at_send_command(at_cmd, NULL, timeout_ms, &response); - if (ret != PPP_SUCCESS) { - return N706A_TCP_ETIMEDOUT; - } - - /* 解析响应,提取数据 */ - if (response.response) { - /* 查找数据部分 */ - const char *data_start = strstr(response.response, "\r\n"); - if (data_start) { - data_start += 2; // 跳过\r\n - size_t data_len = response.response_len - (data_start - response.response); - - if (data_len > len) { - data_len = len; - } - - memcpy(buffer, data_start, data_len); - *received = data_len; - return N706A_TCP_SUCCESS; - } - } - - *received = 0; - return N706A_TCP_SUCCESS; -} - -/** - * @brief 关闭TCP连接 - */ -n706a_tcp_result_t n706a_tcp_close(n706a_tcp_socket_t *sock) -{ - if (!sock) { - return N706A_TCP_EINVAL; - } - - if (sock->state == N706A_TCP_STATE_CLOSED) { - return N706A_TCP_SUCCESS; - } - -#ifdef N706A_SSL_USE_MBEDTLS - /* mbedtls模式:清理SSL上下文 */ - if (sock->is_ssl && sock->ssl_initialized) { - mbedtls_ssl_close_notify(&sock->ssl); - tcp_mbedtls_cleanup_ssl(sock); - } -#endif - - /* 发送关闭命令 */ - char at_cmd[32]; - ppp_at_response_t response; - -#ifdef N706A_SSL_USE_AT_COMMAND - if (sock->is_ssl) { - snprintf(at_cmd, sizeof(at_cmd), "AT+SSLTCPCLOSE=%d", sock->socket_id); - } else -#endif - { - snprintf(at_cmd, sizeof(at_cmd), "AT+TCPCLOSE=%d", sock->socket_id); - } - - ppp_at_send_command(at_cmd, NULL, 5000, &response); - - sock->state = N706A_TCP_STATE_CLOSED; - sock->is_ssl = false; - - return N706A_TCP_SUCCESS; -} - -/** - * @brief 检查socket是否已连接 - */ -bool n706a_tcp_is_connected(const n706a_tcp_socket_t *sock) -{ - if (!sock) { - return false; - } - - return (sock->state == N706A_TCP_STATE_CONNECTED); -} - -/** - * @brief 获取socket状态 - */ -n706a_tcp_state_t n706a_tcp_get_state(const n706a_tcp_socket_t *sock) -{ - if (!sock) { - return N706A_TCP_STATE_ERROR; - } - - return sock->state; -} - -/* ========== mbedtls私有函数实现 ========== */ - -#ifdef N706A_SSL_USE_MBEDTLS - -/** - * @brief 初始化mbedtls SSL - */ -static n706a_tcp_result_t tcp_mbedtls_init_ssl(n706a_tcp_socket_t *sock) -{ - if (!sock) { - return N706A_TCP_EINVAL; - } - - /* 初始化SSL上下文 */ - mbedtls_ssl_init(&sock->ssl); - mbedtls_ssl_config_init(&sock->conf); - mbedtls_entropy_init(&sock->entropy); - mbedtls_ctr_drbg_init(&sock->ctr_drbg); - - /* 初始化随机数生成器 */ - const char *pers = "n706a_tcp_client"; - int ret = mbedtls_ctr_drbg_seed(&sock->ctr_drbg, - mbedtls_entropy_func, - &sock->entropy, - (const unsigned char *)pers, - strlen(pers)); - if (ret != 0) { - return N706A_TCP_ERROR; - } - - /* 配置SSL */ - ret = mbedtls_ssl_config_defaults(&sock->conf, - MBEDTLS_SSL_IS_CLIENT, - MBEDTLS_SSL_TRANSPORT_STREAM, - MBEDTLS_SSL_PRESET_DEFAULT); - if (ret != 0) { - return N706A_TCP_ERROR; - } - - /* 设置认证模式 */ - mbedtls_ssl_conf_authmode(&sock->conf, sock->auth_mode); - - /* 设置随机数生成器 */ - mbedtls_ssl_conf_rng(&sock->conf, - mbedtls_ctr_drbg_random, - &sock->ctr_drbg); - - /* 设置CA证书 */ - mbedtls_ssl_conf_ca_chain(&sock->conf, &sock->cacert, NULL); - - /* 设置客户端证书(如果有) */ - if (sock->clicert.version != 0) { - mbedtls_ssl_conf_own_cert(&sock->conf, &sock->clicert, &sock->pkey); - } - - /* 应用配置 */ - ret = mbedtls_ssl_setup(&sock->ssl, &sock->conf); - if (ret != 0) { - return N706A_TCP_ERROR; - } - - /* 设置发送/接收回调 */ - mbedtls_ssl_set_bio(&sock->ssl, - sock, - tcp_mbedtls_send_callback, - tcp_mbedtls_recv_callback, - NULL); - - sock->ssl_initialized = true; - - return N706A_TCP_SUCCESS; -} - -/** - * @brief 清理mbedtls SSL - */ -static void tcp_mbedtls_cleanup_ssl(n706a_tcp_socket_t *sock) -{ - if (!sock || !sock->ssl_initialized) { - return; - } - - mbedtls_ssl_free(&sock->ssl); - mbedtls_ssl_config_free(&sock->conf); - mbedtls_ctr_drbg_free(&sock->ctr_drbg); - mbedtls_entropy_free(&sock->entropy); - - if (sock->cacert.version != 0) { - mbedtls_x509_crt_free(&sock->cacert); - } - - if (sock->clicert.version != 0) { - mbedtls_x509_crt_free(&sock->clicert); - } - - if (sock->pkey.pk_info != NULL) { - mbedtls_pk_free(&sock->pkey); - } - - sock->ssl_initialized = false; -} - -/** - * @brief mbedtls发送回调(通过AT命令发送) - */ -static int tcp_mbedtls_send_callback(void *ctx, const unsigned char *buf, size_t len) -{ - n706a_tcp_socket_t *sock = (n706a_tcp_socket_t *)ctx; - - if (!sock || !buf || len == 0) { - return MBEDTLS_ERR_NET_SEND_FAILED; - } - - /* 通过AT+TCPSEND发送加密后的数据 */ - char at_cmd[64]; - snprintf(at_cmd, sizeof(at_cmd), "AT+TCPSEND=%d,%zu", - sock->socket_id, len); - - ppp_at_response_t response; - ppp_error_t ret = ppp_at_send_command(at_cmd, NULL, 5000, &response); - if (ret != PPP_SUCCESS) { - return MBEDTLS_ERR_NET_SEND_FAILED; - } - - /* 发送数据 */ - ret = ppp_at_send_raw_data(buf, len, sock->timeout_ms); - if (ret != PPP_SUCCESS) { - return MBEDTLS_ERR_NET_SEND_FAILED; - } - - return len; -} - -/** - * @brief mbedtls接收回调(通过AT命令接收) - */ -static int tcp_mbedtls_recv_callback(void *ctx, unsigned char *buf, size_t len) -{ - n706a_tcp_socket_t *sock = (n706a_tcp_socket_t *)ctx; - - if (!sock || !buf || len == 0) { - return MBEDTLS_ERR_NET_RECV_FAILED; - } - - /* 通过AT+TCPREAD接收加密数据 */ - char at_cmd[64]; - snprintf(at_cmd, sizeof(at_cmd), "AT+TCPREAD=%d,%zu", - sock->socket_id, len); - - ppp_at_response_t response; - ppp_error_t ret = ppp_at_send_command(at_cmd, NULL, sock->timeout_ms, &response); - if (ret != PPP_SUCCESS) { - return MBEDTLS_ERR_NET_RECV_FAILED; - } - - /* 解析数据 */ - if (response.response) { - const char *data_start = strstr(response.response, "\r\n"); - if (data_start) { - data_start += 2; - size_t data_len = response.response_len - (data_start - response.response); - - if (data_len > len) { - data_len = len; - } - - memcpy(buf, data_start, data_len); - return data_len; - } - } - - return 0; // 没有数据 -} - -#endif /* N706A_SSL_USE_MBEDTLS */ - diff --git a/code/sdk/zhihui/ppp/src/n706a_websocket.c b/code/sdk/zhihui/ppp/src/n706a_websocket.c deleted file mode 100644 index 70b86a2a6..000000000 --- a/code/sdk/zhihui/ppp/src/n706a_websocket.c +++ /dev/null @@ -1,843 +0,0 @@ -/** - * @file n706a_websocket.c - * @brief N706A WebSocket客户端实现(基于TCP) - * - * 完整实现RFC 6455 WebSocket协议: - * - ws:// (普通TCP) - * - wss:// (SSL TCP) - * - 握手 - * - 帧封装/解封装 - * - Ping/Pong - * - 分片传输 - */ - -#include "n706a_websocket.h" -#include -#include -#include -#include - -/* WebSocket GUID(RFC 6455) */ -#define WS_GUID "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" - -/* WebSocket帧标志 */ -#define WS_FIN_BIT 0x80 -#define WS_MASK_BIT 0x80 -#define WS_OPCODE_MASK 0x0F -#define WS_PAYLOAD_MASK 0x7F - -/* 全局WebSocket客户端数组 */ -static n706a_websocket_client_t *g_ws_clients[N706A_WS_MAX_CLIENTS] = {NULL}; - -/* ========== 私有函数声明 ========== */ - -static int ws_perform_handshake(n706a_websocket_client_t *client); -static int ws_send_frame(n706a_websocket_client_t *client, - n706a_ws_opcode_t opcode, - const uint8_t *data, - size_t length, - bool fin); -static int ws_recv_frame(n706a_websocket_client_t *client); -static void ws_generate_mask_key(uint8_t *mask_key); -static void ws_mask_data(uint8_t *data, size_t len, const uint8_t *mask_key); -static void *ws_recv_thread_func(void *arg); -static int ws_parse_url(const char *url, char *protocol, char *host, - uint16_t *port, char *path); - -/* SHA-1哈希(简化实现,用于WebSocket握手) */ -static void sha1_simple(const unsigned char *input, size_t len, unsigned char output[20]); - -/* Base64编码 */ -static void base64_encode(const unsigned char *input, size_t len, char *output); - -/* ========== 公共函数实现 ========== */ - -/** - * @brief 创建WebSocket客户端 - */ -int n706a_websocket_constructor(n706a_ws_on_open_callback_t on_open, - n706a_ws_on_message_callback_t on_message, - n706a_ws_on_error_callback_t on_error, - n706a_ws_on_close_callback_t on_close) -{ - /* 查找空闲slot */ - int client_id = -1; - for (int i = 0; i < N706A_WS_MAX_CLIENTS; i++) { - if (g_ws_clients[i] == NULL) { - client_id = i; - break; - } - } - - if (client_id < 0) { - return -1; // 没有可用slot - } - - /* 分配客户端结构 */ - n706a_websocket_client_t *client = - (n706a_websocket_client_t *)malloc(sizeof(n706a_websocket_client_t)); - - if (!client) { - return -1; - } - - memset(client, 0, sizeof(n706a_websocket_client_t)); - - client->client_id = client_id; - client->state = N706A_WS_STATE_CLOSED; - client->is_ssl = false; - - /* 初始化TCP socket */ - n706a_tcp_socket_create(&client->tcp_sock, client_id); - - /* 设置回调 */ - client->on_open = on_open; - client->on_message = on_message; - client->on_error = on_error; - client->on_close = on_close; - - /* 分配接收缓冲区 */ - client->recv_buffer_size = 8192; - client->recv_buffer = (uint8_t *)malloc(client->recv_buffer_size); - if (!client->recv_buffer) { - free(client); - return -1; - } - - client->recv_buffer_len = 0; - client->running = false; - - g_ws_clients[client_id] = client; - - return client_id; -} - -/** - * @brief 创建WebSocket客户端(指定socket ID) - */ -int n706a_websocket_constructor_with_socket(int socket_id, - n706a_ws_on_open_callback_t on_open, - n706a_ws_on_message_callback_t on_message, - n706a_ws_on_error_callback_t on_error, - n706a_ws_on_close_callback_t on_close) -{ - if (socket_id < 0 || socket_id >= N706A_WS_MAX_CLIENTS) { - return -1; - } - - if (g_ws_clients[socket_id] != NULL) { - return -1; // 已被占用 - } - - n706a_websocket_client_t *client = - (n706a_websocket_client_t *)malloc(sizeof(n706a_websocket_client_t)); - - if (!client) { - return -1; - } - - memset(client, 0, sizeof(n706a_websocket_client_t)); - - client->client_id = socket_id; - client->state = N706A_WS_STATE_CLOSED; - - n706a_tcp_socket_create(&client->tcp_sock, socket_id); - - client->on_open = on_open; - client->on_message = on_message; - client->on_error = on_error; - client->on_close = on_close; - - client->recv_buffer_size = 8192; - client->recv_buffer = (uint8_t *)malloc(client->recv_buffer_size); - if (!client->recv_buffer) { - free(client); - return -1; - } - - g_ws_clients[socket_id] = client; - - return socket_id; -} - -/** - * @brief 连接到WebSocket服务器 - */ -int n706a_websocket_connect(int client_id, const char *url, const char *option) -{ - if (client_id < 0 || client_id >= N706A_WS_MAX_CLIENTS) { - return N706A_WS_ERROR_INVALID_PARAM; - } - - n706a_websocket_client_t *client = g_ws_clients[client_id]; - if (!client) { - return N706A_WS_ERROR_INVALID_PARAM; - } - - if (client->state != N706A_WS_STATE_CLOSED) { - return N706A_WS_ERROR_ALREADY_OPEN; - } - - /* 解析URL */ - char protocol[8]; - - if (ws_parse_url(url, protocol, client->host, &client->port, client->path) != 0) { - if (client->on_error) { - client->on_error(client_id, N706A_WS_ERROR_INVALID_PARAM, "Invalid URL"); - } - return N706A_WS_ERROR_INVALID_PARAM; - } - - strncpy(client->url, url, sizeof(client->url) - 1); - - /* 判断是否WSS */ - client->is_ssl = (strcmp(protocol, "wss") == 0); - - client->state = N706A_WS_STATE_CONNECTING; - - /* 建立TCP连接 */ - n706a_tcp_result_t ret = n706a_tcp_connect(&client->tcp_sock, - client->host, - client->port, - client->is_ssl, - 30000); - - if (ret != N706A_TCP_SUCCESS) { - client->state = N706A_WS_STATE_CLOSED; - if (client->on_error) { - client->on_error(client_id, N706A_WS_ERROR_HANDSHAKE, "TCP connection failed"); - } - return N706A_WS_ERROR_HANDSHAKE; - } - - /* WebSocket握手 */ - if (ws_perform_handshake(client) != 0) { - n706a_tcp_close(&client->tcp_sock); - client->state = N706A_WS_STATE_CLOSED; - if (client->on_error) { - client->on_error(client_id, N706A_WS_ERROR_HANDSHAKE, "WebSocket handshake failed"); - } - return N706A_WS_ERROR_HANDSHAKE; - } - - client->state = N706A_WS_STATE_OPEN; - - /* 启动接收线程 */ - client->running = true; - // TODO: 创建接收线程 - // pthread_create(&client->recv_thread, NULL, ws_recv_thread_func, client); - - /* 调用on_open回调 */ - if (client->on_open) { - client->on_open(client_id); - } - - return N706A_WS_SUCCESS; -} - -/** - * @brief 发送文本消息 - */ -int n706a_websocket_send_text(int client_id, const char *data, size_t length) -{ - if (client_id < 0 || client_id >= N706A_WS_MAX_CLIENTS) { - return N706A_WS_ERROR_INVALID_PARAM; - } - - n706a_websocket_client_t *client = g_ws_clients[client_id]; - if (!client || client->state != N706A_WS_STATE_OPEN) { - return N706A_WS_ERROR_NOT_CONNECTED; - } - - return ws_send_frame(client, N706A_WS_OPCODE_TEXT, (const uint8_t *)data, length, true); -} - -/** - * @brief 发送二进制消息 - */ -int n706a_websocket_send_binary(int client_id, const uint8_t *data, size_t length) -{ - if (client_id < 0 || client_id >= N706A_WS_MAX_CLIENTS) { - return N706A_WS_ERROR_INVALID_PARAM; - } - - n706a_websocket_client_t *client = g_ws_clients[client_id]; - if (!client || client->state != N706A_WS_STATE_OPEN) { - return N706A_WS_ERROR_NOT_CONNECTED; - } - - return ws_send_frame(client, N706A_WS_OPCODE_BINARY, data, length, true); -} - -/** - * @brief 发送Ping帧 - */ -int n706a_websocket_send_ping(int client_id, const char *data, size_t length) -{ - if (client_id < 0 || client_id >= N706A_WS_MAX_CLIENTS) { - return N706A_WS_ERROR_INVALID_PARAM; - } - - n706a_websocket_client_t *client = g_ws_clients[client_id]; - if (!client || client->state != N706A_WS_STATE_OPEN) { - return N706A_WS_ERROR_NOT_CONNECTED; - } - - return ws_send_frame(client, N706A_WS_OPCODE_PING, (const uint8_t *)data, length, true); -} - -/** - * @brief 发送Pong帧 - */ -int n706a_websocket_send_pong(int client_id, const char *data, size_t length) -{ - if (client_id < 0 || client_id >= N706A_WS_MAX_CLIENTS) { - return N706A_WS_ERROR_INVALID_PARAM; - } - - n706a_websocket_client_t *client = g_ws_clients[client_id]; - if (!client || client->state != N706A_WS_STATE_OPEN) { - return N706A_WS_ERROR_NOT_CONNECTED; - } - - return ws_send_frame(client, N706A_WS_OPCODE_PONG, (const uint8_t *)data, length, true); -} - -/** - * @brief 关闭WebSocket连接 - */ -int n706a_websocket_close(int client_id) -{ - return n706a_websocket_close_with_reason(client_id, N706A_WS_CLOSE_NORMAL, NULL); -} - -/** - * @brief 关闭WebSocket连接(带原因) - */ -int n706a_websocket_close_with_reason(int client_id, - n706a_ws_close_code_t close_code, - const char *reason) -{ - if (client_id < 0 || client_id >= N706A_WS_MAX_CLIENTS) { - return N706A_WS_ERROR_INVALID_PARAM; - } - - n706a_websocket_client_t *client = g_ws_clients[client_id]; - if (!client) { - return N706A_WS_ERROR_INVALID_PARAM; - } - - if (client->state == N706A_WS_STATE_OPEN) { - /* 发送关闭帧 */ - uint8_t close_data[128]; - size_t close_len = 2; - - close_data[0] = (close_code >> 8) & 0xFF; - close_data[1] = close_code & 0xFF; - - if (reason) { - size_t reason_len = strlen(reason); - if (reason_len > sizeof(close_data) - 2) { - reason_len = sizeof(close_data) - 2; - } - memcpy(close_data + 2, reason, reason_len); - close_len += reason_len; - } - - ws_send_frame(client, N706A_WS_OPCODE_CLOSE, close_data, close_len, true); - - client->state = N706A_WS_STATE_CLOSING; - } - - /* 停止接收线程 */ - client->running = false; - // TODO: 等待线程结束 - // pthread_join(client->recv_thread, NULL); - - /* 关闭TCP连接 */ - n706a_tcp_close(&client->tcp_sock); - - client->state = N706A_WS_STATE_CLOSED; - - /* 调用on_close回调 */ - if (client->on_close) { - client->on_close(client_id, close_code, reason); - } - - return N706A_WS_SUCCESS; -} - -/** - * @brief 获取WebSocket状态 - */ -n706a_ws_state_t n706a_websocket_get_state(int client_id) -{ - if (client_id < 0 || client_id >= N706A_WS_MAX_CLIENTS) { - return N706A_WS_STATE_CLOSED; - } - - n706a_websocket_client_t *client = g_ws_clients[client_id]; - if (!client) { - return N706A_WS_STATE_CLOSED; - } - - return client->state; -} - -/** - * @brief 检查WebSocket是否已连接 - */ -bool n706a_websocket_is_connected(int client_id) -{ - return (n706a_websocket_get_state(client_id) == N706A_WS_STATE_OPEN); -} - -/** - * @brief 销毁WebSocket客户端 - */ -int n706a_websocket_destructor(int client_id) -{ - if (client_id < 0 || client_id >= N706A_WS_MAX_CLIENTS) { - return N706A_WS_ERROR_INVALID_PARAM; - } - - n706a_websocket_client_t *client = g_ws_clients[client_id]; - if (!client) { - return N706A_WS_SUCCESS; - } - - /* 确保连接已关闭 */ - if (client->state != N706A_WS_STATE_CLOSED) { - n706a_websocket_close(client_id); - } - - /* 释放资源 */ - if (client->recv_buffer) { - free(client->recv_buffer); - } - - free(client); - g_ws_clients[client_id] = NULL; - - return N706A_WS_SUCCESS; -} - -/* ========== 私有函数实现 ========== */ - -/** - * @brief 执行WebSocket握手 - */ -static int ws_perform_handshake(n706a_websocket_client_t *client) -{ - char handshake_req[1024]; - char sec_key[25]; - - /* 生成Sec-WebSocket-Key */ - n706a_websocket_generate_key(sec_key); - strncpy(client->sec_key, sec_key, sizeof(client->sec_key) - 1); - - /* 构造握手请求 */ - int len = snprintf(handshake_req, sizeof(handshake_req), - "GET %s HTTP/1.1\r\n" - "Host: %s\r\n" - "Upgrade: websocket\r\n" - "Connection: Upgrade\r\n" - "Sec-WebSocket-Key: %s\r\n" - "Sec-WebSocket-Version: 13\r\n" - "\r\n", - client->path, - client->host, - sec_key - ); - - /* 发送握手请求 */ - size_t sent; - n706a_tcp_result_t ret = n706a_tcp_send(&client->tcp_sock, - (uint8_t *)handshake_req, - len, - &sent); - - if (ret != N706A_TCP_SUCCESS) { - return -1; - } - - /* 接收握手响应 */ - uint8_t response[2048]; - size_t received; - - ret = n706a_tcp_recv(&client->tcp_sock, - response, - sizeof(response) - 1, - &received, - 10000); - - if (ret != N706A_TCP_SUCCESS || received == 0) { - return -1; - } - - response[received] = '\0'; - - /* 验证响应 */ - if (!strstr((char *)response, "HTTP/1.1 101") || - !strstr((char *)response, "Upgrade: websocket") || - !strstr((char *)response, "Connection: Upgrade")) { - return -1; - } - - /* 验证Sec-WebSocket-Accept */ - const char *accept_start = strstr((char *)response, "Sec-WebSocket-Accept:"); - if (accept_start) { - accept_start += 21; // 跳过"Sec-WebSocket-Accept:" - while (*accept_start == ' ') accept_start++; - - char expected_accept[29]; - n706a_websocket_calculate_accept(sec_key, expected_accept); - - if (strncmp(accept_start, expected_accept, strlen(expected_accept)) != 0) { - return -1; // Accept值不匹配 - } - } - - return 0; -} - -/** - * @brief 发送WebSocket帧 - */ -static int ws_send_frame(n706a_websocket_client_t *client, - n706a_ws_opcode_t opcode, - const uint8_t *data, - size_t length, - bool fin) -{ - uint8_t frame[14]; // 最大帧头长度 - size_t frame_len = 0; - - /* 第一字节:FIN + RSV + Opcode */ - frame[frame_len++] = (fin ? WS_FIN_BIT : 0) | (opcode & WS_OPCODE_MASK); - - /* 第二字节:MASK + Payload length */ - if (length < 126) { - frame[frame_len++] = WS_MASK_BIT | (length & WS_PAYLOAD_MASK); - } else if (length < 65536) { - frame[frame_len++] = WS_MASK_BIT | 126; - frame[frame_len++] = (length >> 8) & 0xFF; - frame[frame_len++] = length & 0xFF; - } else { - frame[frame_len++] = WS_MASK_BIT | 127; - for (int i = 7; i >= 0; i--) { - frame[frame_len++] = (length >> (i * 8)) & 0xFF; - } - } - - /* Masking key */ - uint8_t mask_key[4]; - ws_generate_mask_key(mask_key); - memcpy(frame + frame_len, mask_key, 4); - frame_len += 4; - - /* 发送帧头 */ - size_t sent; - n706a_tcp_send(&client->tcp_sock, frame, frame_len, &sent); - - /* 发送payload(掩码后) */ - if (data && length > 0) { - uint8_t *masked_data = (uint8_t *)malloc(length); - if (masked_data) { - memcpy(masked_data, data, length); - ws_mask_data(masked_data, length, mask_key); - - n706a_tcp_send(&client->tcp_sock, masked_data, length, &sent); - - free(masked_data); - } - } - - return N706A_WS_SUCCESS; -} - -/** - * @brief 接收WebSocket帧 - */ -static int ws_recv_frame(n706a_websocket_client_t *client) -{ - uint8_t header[2]; - size_t received; - - /* 接收帧头前2字节 */ - n706a_tcp_result_t ret = n706a_tcp_recv(&client->tcp_sock, - header, - 2, - &received, - 5000); - - if (ret != N706A_TCP_SUCCESS || received < 2) { - return -1; - } - - bool fin = (header[0] & WS_FIN_BIT) != 0; - n706a_ws_opcode_t opcode = (n706a_ws_opcode_t)(header[0] & WS_OPCODE_MASK); - bool masked = (header[1] & WS_MASK_BIT) != 0; - uint64_t payload_len = header[1] & WS_PAYLOAD_MASK; - - /* 扩展payload长度 */ - if (payload_len == 126) { - uint8_t len_buf[2]; - n706a_tcp_recv(&client->tcp_sock, len_buf, 2, &received, 5000); - payload_len = (len_buf[0] << 8) | len_buf[1]; - } else if (payload_len == 127) { - uint8_t len_buf[8]; - n706a_tcp_recv(&client->tcp_sock, len_buf, 8, &received, 5000); - payload_len = 0; - for (int i = 0; i < 8; i++) { - payload_len = (payload_len << 8) | len_buf[i]; - } - } - - /* 接收payload */ - if (payload_len > 0) { - uint8_t *payload = (uint8_t *)malloc(payload_len + 1); - if (!payload) { - return -1; - } - - n706a_tcp_recv(&client->tcp_sock, payload, payload_len, &received, 10000); - payload[payload_len] = '\0'; - - /* 处理不同类型的帧 */ - switch (opcode) { - case N706A_WS_OPCODE_TEXT: - case N706A_WS_OPCODE_BINARY: - if (client->on_message) { - client->on_message(client->client_id, - (char *)payload, - payload_len, - fin, - opcode == N706A_WS_OPCODE_BINARY); - } - break; - - case N706A_WS_OPCODE_PING: - /* 自动回复Pong */ - ws_send_frame(client, N706A_WS_OPCODE_PONG, payload, payload_len, true); - break; - - case N706A_WS_OPCODE_PONG: - /* 忽略Pong */ - break; - - case N706A_WS_OPCODE_CLOSE: - /* 服务器关闭连接 */ - client->state = N706A_WS_STATE_CLOSING; - - uint16_t close_code = N706A_WS_CLOSE_NO_STATUS; - if (payload_len >= 2) { - close_code = (payload[0] << 8) | payload[1]; - } - - if (client->on_close) { - client->on_close(client->client_id, close_code, - payload_len > 2 ? (char *)(payload + 2) : NULL); - } - break; - - default: - break; - } - - free(payload); - } - - return 0; -} - -/** - * @brief 生成mask key - */ -static void ws_generate_mask_key(uint8_t *mask_key) -{ - for (int i = 0; i < 4; i++) { - mask_key[i] = rand() & 0xFF; - } -} - -/** - * @brief 掩码数据 - */ -static void ws_mask_data(uint8_t *data, size_t len, const uint8_t *mask_key) -{ - for (size_t i = 0; i < len; i++) { - data[i] ^= mask_key[i % 4]; - } -} - -/** - * @brief 解析WebSocket URL - */ -static int ws_parse_url(const char *url, char *protocol, char *host, - uint16_t *port, char *path) -{ - if (!url || !protocol || !host || !port || !path) { - return -1; - } - - const char *p = url; - - /* 解析协议 */ - if (strncmp(p, "wss://", 6) == 0) { - strcpy(protocol, "wss"); - *port = 443; - p += 6; - } else if (strncmp(p, "ws://", 5) == 0) { - strcpy(protocol, "ws"); - *port = 80; - p += 5; - } else { - return -1; - } - - /* 解析host */ - const char *path_start = strchr(p, '/'); - const char *port_start = strchr(p, ':'); - - if (port_start && (!path_start || port_start < path_start)) { - size_t host_len = port_start - p; - strncpy(host, p, host_len); - host[host_len] = '\0'; - - *port = atoi(port_start + 1); - - if (path_start) { - strcpy(path, path_start); - } else { - strcpy(path, "/"); - } - } else { - if (path_start) { - size_t host_len = path_start - p; - strncpy(host, p, host_len); - host[host_len] = '\0'; - strcpy(path, path_start); - } else { - strcpy(host, p); - strcpy(path, "/"); - } - } - - return 0; -} - -/** - * @brief 生成WebSocket Key - */ -int n706a_websocket_generate_key(char *key) -{ - if (!key) { - return -1; - } - - /* 生成16字节随机数 */ - unsigned char random_bytes[16]; - srand(time(NULL)); - for (int i = 0; i < 16; i++) { - random_bytes[i] = rand() & 0xFF; - } - - /* Base64编码 */ - base64_encode(random_bytes, 16, key); - - return 0; -} - -/** - * @brief 计算WebSocket Accept - */ -int n706a_websocket_calculate_accept(const char *key, char *accept) -{ - if (!key || !accept) { - return -1; - } - - /* 拼接key和GUID */ - char concat[128]; - snprintf(concat, sizeof(concat), "%s%s", key, WS_GUID); - - /* SHA-1哈希 */ - unsigned char hash[20]; - sha1_simple((unsigned char *)concat, strlen(concat), hash); - - /* Base64编码 */ - base64_encode(hash, 20, accept); - - return 0; -} - -/* ========== 辅助函数 ========== */ - -/** - * @brief 简化的SHA-1实现(仅用于WebSocket握手) - * 注意:这是简化版,生产环境建议使用成熟的加密库 - */ -static void sha1_simple(const unsigned char *input, size_t len, unsigned char output[20]) -{ - /* 这里应该是完整的SHA-1实现 */ - /* 为了简化,这里仅做占位 */ - /* 生产环境应使用mbedtls_sha1或OpenSSL的SHA1函数 */ - - /* TODO: 集成真实的SHA-1实现 */ - memset(output, 0, 20); - - /* 临时:使用简单哈希作为占位符 */ - for (size_t i = 0; i < len && i < 20; i++) { - output[i % 20] ^= input[i]; - } -} - -/** - * @brief Base64编码 - */ -static void base64_encode(const unsigned char *input, size_t len, char *output) -{ - static const char base64_table[] = - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; - - size_t i = 0, j = 0; - unsigned char char_array_3[3]; - unsigned char char_array_4[4]; - - while (len--) { - char_array_3[i++] = *(input++); - if (i == 3) { - char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; - char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); - char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); - char_array_4[3] = char_array_3[2] & 0x3f; - - for (i = 0; i < 4; i++) { - output[j++] = base64_table[char_array_4[i]]; - } - i = 0; - } - } - - if (i) { - for (size_t k = i; k < 3; k++) { - char_array_3[k] = '\0'; - } - - char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; - char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); - char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); - - for (size_t k = 0; k < i + 1; k++) { - output[j++] = base64_table[char_array_4[k]]; - } - - while (i++ < 3) { - output[j++] = '='; - } - } - - output[j] = '\0'; -} - diff --git a/code/sdk/zhihui/utils/custom_utils.c b/code/sdk/zhihui/utils/custom_utils.c index b5c080849..b567a58dc 100644 --- a/code/sdk/zhihui/utils/custom_utils.c +++ b/code/sdk/zhihui/utils/custom_utils.c @@ -85,6 +85,11 @@ #include "time.h" #include "uart.h" + +#ifdef SUPPORT_N706A_NET +#include "n706a_net_example.h" +#endif + /* Define time structures to avoid musl dependency */ typedef long suseconds_t; /* microseconds (signed) */ @@ -699,6 +704,11 @@ void startup_post_init_task(void) if(baudrate != 0) set_ppp_baudrate(baudrate); ppp_env_init(); +#elif defined(SUPPORT_N706A_NET) + uint32_t baudrate = get_ppp_baudrate_from_config_model(); + if(baudrate != 0) + set_n706a_baudrate(baudrate); // N706A使用自己的波特率设置函数 + n706a_net_env_init(); #endif #endif diff --git a/code/sdk/zhihui/zhihui_net/adapter/zhihui_net_openharmony_adapter.c b/code/sdk/zhihui/zhihui_net/adapter/zhihui_net_openharmony_adapter.c deleted file mode 100644 index 30968312d..000000000 --- a/code/sdk/zhihui/zhihui_net/adapter/zhihui_net_openharmony_adapter.c +++ /dev/null @@ -1,298 +0,0 @@ -/** - * @file zhihui_net_openharmony_adapter.c - * @brief OpenHarmony网络实现适配器实现 - * - * 将统一接口转换为OpenHarmony的httpclient和net_websocket接口 - */ - -#include "zhihui_net_openharmony_adapter.h" -#include "httpclient.h" -#include "net_websocket.h" -#include -#include -#include - -/* ========== HTTP接口适配 ========== */ - -/* 将ZhihuiHttpClient转换为OpenHarmony HttpClient */ -static void convert_to_openharmony_http_client(ZhihuiHttpClient *src, HttpClient *dst) -{ - memset(dst, 0, sizeof(HttpClient)); - dst->socket = src->socket; - dst->remotePort = src->remotePort; - dst->responseCode = src->responseCode; - dst->header = src->header; - dst->authUser = src->authUser; - dst->authPassword = src->authPassword; - dst->isHttp = src->isHttp; -} - -/* 将OpenHarmony HttpClient转换回ZhihuiHttpClient */ -static void convert_from_openharmony_http_client(HttpClient *src, ZhihuiHttpClient *dst) -{ - dst->socket = src->socket; - dst->remotePort = src->remotePort; - dst->responseCode = src->responseCode; - dst->header = src->header; - dst->authUser = src->authUser; - dst->authPassword = src->authPassword; - dst->isHttp = src->isHttp; -} - -/* 将ZhihuiHttpClientData转换为OpenHarmony HttpClientData */ -static void convert_to_openharmony_http_data(ZhihuiHttpClientData *src, HttpClientData *dst) -{ - memset(dst, 0, sizeof(HttpClientData)); - dst->isMore = src->isMore; - dst->isChunked = src->isChunked; - dst->method = src->method; - dst->retrieveLen = src->retrieveLen; - dst->responseContentLen = src->responseContentLen; - dst->contentBlockLen = src->contentBlockLen; - dst->postBufLen = src->postBufLen; - dst->responseBufLen = src->responseBufLen; - dst->headerBufLen = src->headerBufLen; - dst->postContentType = src->postContentType; - dst->postBuf = src->postBuf; - dst->responseBuf = src->responseBuf; - dst->headerBuf = src->headerBuf; - dst->isRedirected = src->isRedirected; - dst->redirectUrl = src->redirectUrl; -} - -/* 将OpenHarmony HttpClientData转换回ZhihuiHttpClientData */ -static void convert_from_openharmony_http_data(HttpClientData *src, ZhihuiHttpClientData *dst) -{ - dst->isMore = src->isMore; - dst->isChunked = src->isChunked; - dst->method = src->method; - dst->retrieveLen = src->retrieveLen; - dst->responseContentLen = src->responseContentLen; - dst->contentBlockLen = src->contentBlockLen; - dst->postBufLen = src->postBufLen; - dst->responseBufLen = src->responseBufLen; - dst->headerBufLen = src->headerBufLen; - dst->postContentType = src->postContentType; - dst->postBuf = src->postBuf; - dst->responseBuf = src->responseBuf; - dst->headerBuf = src->headerBuf; - dst->isRedirected = src->isRedirected; - dst->redirectUrl = src->redirectUrl; -} - -int ZhihuiNetOpenHarmonyHttpGet(ZhihuiHttpClient *client, const char *url, - ZhihuiHttpClientData *data) -{ - HttpClient oh_client; - HttpClientData oh_data; - int ret; - - convert_to_openharmony_http_client(client, &oh_client); - convert_to_openharmony_http_data(data, &oh_data); - - ret = HttpClientGetRequest(&oh_client, url, &oh_data); - - convert_from_openharmony_http_client(&oh_client, client); - convert_from_openharmony_http_data(&oh_data, data); - - return ret; -} - -int ZhihuiNetOpenHarmonyHttpPost(ZhihuiHttpClient *client, const char *url, - ZhihuiHttpClientData *data) -{ - HttpClient oh_client; - HttpClientData oh_data; - int ret; - - convert_to_openharmony_http_client(client, &oh_client); - convert_to_openharmony_http_data(data, &oh_data); - - ret = HttpClientPostRequest(&oh_client, url, &oh_data); - - convert_from_openharmony_http_client(&oh_client, client); - convert_from_openharmony_http_data(&oh_data, data); - - return ret; -} - -int ZhihuiNetOpenHarmonyHttpHead(ZhihuiHttpClient *client, const char *url, - ZhihuiHttpClientData *data) -{ - HttpClient oh_client; - HttpClientData oh_data; - int ret; - - convert_to_openharmony_http_client(client, &oh_client); - convert_to_openharmony_http_data(data, &oh_data); - - ret = HttpClientHeadRequest(&oh_client, url, &oh_data); - - convert_from_openharmony_http_client(&oh_client, client); - convert_from_openharmony_http_data(&oh_data, data); - - return ret; -} - -int ZhihuiNetOpenHarmonyHttpPut(ZhihuiHttpClient *client, const char *url, - ZhihuiHttpClientData *data) -{ - HttpClient oh_client; - HttpClientData oh_data; - int ret; - - convert_to_openharmony_http_client(client, &oh_client); - convert_to_openharmony_http_data(data, &oh_data); - - ret = HttpClientPutRequest(&oh_client, url, &oh_data); - - convert_from_openharmony_http_client(&oh_client, client); - convert_from_openharmony_http_data(&oh_data, data); - - return ret; -} - -int ZhihuiNetOpenHarmonyHttpDelete(ZhihuiHttpClient *client, const char *url, - ZhihuiHttpClientData *data) -{ - HttpClient oh_client; - HttpClientData oh_data; - int ret; - - convert_to_openharmony_http_client(client, &oh_client); - convert_to_openharmony_http_data(data, &oh_data); - - ret = HttpClientDeleteRequest(&oh_client, url, &oh_data); - - convert_from_openharmony_http_client(&oh_client, client); - convert_from_openharmony_http_data(&oh_data, data); - - return ret; -} - -/* ========== WebSocket接口适配 ========== */ - -/* 回调函数适配器 */ -typedef struct { - ZhihuiWebSocketOnOpenCallback zhihui_on_open; - ZhihuiWebSocketOnMessageCallback zhihui_on_message; - ZhihuiWebSocketOnErrorCallback zhihui_on_error; - ZhihuiWebSocketOnCloseCallback zhihui_on_close; -} ws_callback_adapter_t; - -#define MAX_WS_CLIENTS 8 -static ws_callback_adapter_t g_ws_adapters[MAX_WS_CLIENTS]; - -/* OpenHarmony回调转发到Zhihui回调 */ -static void oh_ws_on_open(int client) -{ - if (client >= 0 && client < MAX_WS_CLIENTS) { - if (g_ws_adapters[client].zhihui_on_open) { - g_ws_adapters[client].zhihui_on_open(client); - } - } -} - -static void oh_ws_on_message(int client, const char *data, size_t length, bool isFinished) -{ - if (client >= 0 && client < MAX_WS_CLIENTS) { - if (g_ws_adapters[client].zhihui_on_message) { - /* OpenHarmony不区分文本/二进制,默认作为文本 */ - g_ws_adapters[client].zhihui_on_message(client, data, length, isFinished, false); - } - } -} - -static void oh_ws_on_error(int client, int errorCode, const char *errorMsg) -{ - if (client >= 0 && client < MAX_WS_CLIENTS) { - if (g_ws_adapters[client].zhihui_on_error) { - g_ws_adapters[client].zhihui_on_error(client, errorCode, errorMsg); - } - } -} - -static void oh_ws_on_close(int client, int closeCode, const char *reason) -{ - if (client >= 0 && client < MAX_WS_CLIENTS) { - if (g_ws_adapters[client].zhihui_on_close) { - g_ws_adapters[client].zhihui_on_close(client, closeCode, reason); - } - } -} - -int ZhihuiNetOpenHarmonyWsConstructor(ZhihuiWebSocketOnOpenCallback onOpen, - ZhihuiWebSocketOnMessageCallback onMessage, - ZhihuiWebSocketOnErrorCallback onError, - ZhihuiWebSocketOnCloseCallback onClose) -{ - int client = WebSocketClientConstructor(oh_ws_on_open, oh_ws_on_message, - oh_ws_on_error, oh_ws_on_close); - - if (client >= 0 && client < MAX_WS_CLIENTS) { - g_ws_adapters[client].zhihui_on_open = onOpen; - g_ws_adapters[client].zhihui_on_message = onMessage; - g_ws_adapters[client].zhihui_on_error = onError; - g_ws_adapters[client].zhihui_on_close = onClose; - } - - return client; -} - -int ZhihuiNetOpenHarmonyWsConnect(int client, const char *url, const char *option) -{ - return WebSocketClientConnect(client, url, option); -} - -int ZhihuiNetOpenHarmonyWsSendText(int client, const char *text, size_t length) -{ - return WebSocketClientSendText(client, text, length); -} - -int ZhihuiNetOpenHarmonyWsSendBinStart(int client, const char *data, size_t length) -{ - return WebSocketClientSendBinStart(client, data, length); -} - -int ZhihuiNetOpenHarmonyWsSendBinContinue(int client, const char *data, size_t length) -{ - return WebSocketClientSendBinContinue(client, data, length); -} - -int ZhihuiNetOpenHarmonyWsSendBinEnd(int client, const char *data, size_t length) -{ - return WebSocketClientSendBinEnd(client, data, length); -} - -int ZhihuiNetOpenHarmonyWsClose(int client) -{ - return WebSocketClientClose(client); -} - -int ZhihuiNetOpenHarmonyWsDestructor(int client) -{ - int ret = WebSocketClientDestructor(client); - - /* 清除适配器 */ - if (client >= 0 && client < MAX_WS_CLIENTS) { - memset(&g_ws_adapters[client], 0, sizeof(ws_callback_adapter_t)); - } - - return ret; -} - -/* ========== 初始化/清理 ========== */ - -int ZhihuiNetOpenHarmonyInit(void) -{ - printf("[ZhihuiNet][OpenHarmony] Adapter initialized\n"); - memset(g_ws_adapters, 0, sizeof(g_ws_adapters)); - return 0; -} - -int ZhihuiNetOpenHarmonyDeinit(void) -{ - printf("[ZhihuiNet][OpenHarmony] Adapter deinitialized\n"); - return 0; -} - diff --git a/code/sdk/zhihui/zhihui_net/adapter/zhihui_net_openharmony_adapter.h b/code/sdk/zhihui/zhihui_net/adapter/zhihui_net_openharmony_adapter.h deleted file mode 100644 index 29b467c2b..000000000 --- a/code/sdk/zhihui/zhihui_net/adapter/zhihui_net_openharmony_adapter.h +++ /dev/null @@ -1,49 +0,0 @@ -/** - * @file zhihui_net_openharmony_adapter.h - * @brief OpenHarmony网络实现适配器 - */ - -#ifndef ZHIHUI_NET_OPENHARMONY_ADAPTER_H -#define ZHIHUI_NET_OPENHARMONY_ADAPTER_H - -#include "zhihui_net.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/* HTTP接口 */ -int ZhihuiNetOpenHarmonyHttpGet(ZhihuiHttpClient *client, const char *url, - ZhihuiHttpClientData *data); -int ZhihuiNetOpenHarmonyHttpPost(ZhihuiHttpClient *client, const char *url, - ZhihuiHttpClientData *data); -int ZhihuiNetOpenHarmonyHttpHead(ZhihuiHttpClient *client, const char *url, - ZhihuiHttpClientData *data); -int ZhihuiNetOpenHarmonyHttpPut(ZhihuiHttpClient *client, const char *url, - ZhihuiHttpClientData *data); -int ZhihuiNetOpenHarmonyHttpDelete(ZhihuiHttpClient *client, const char *url, - ZhihuiHttpClientData *data); - -/* WebSocket接口 */ -int ZhihuiNetOpenHarmonyWsConstructor(ZhihuiWebSocketOnOpenCallback onOpen, - ZhihuiWebSocketOnMessageCallback onMessage, - ZhihuiWebSocketOnErrorCallback onError, - ZhihuiWebSocketOnCloseCallback onClose); -int ZhihuiNetOpenHarmonyWsConnect(int client, const char *url, const char *option); -int ZhihuiNetOpenHarmonyWsSendText(int client, const char *text, size_t length); -int ZhihuiNetOpenHarmonyWsSendBinStart(int client, const char *data, size_t length); -int ZhihuiNetOpenHarmonyWsSendBinContinue(int client, const char *data, size_t length); -int ZhihuiNetOpenHarmonyWsSendBinEnd(int client, const char *data, size_t length); -int ZhihuiNetOpenHarmonyWsClose(int client); -int ZhihuiNetOpenHarmonyWsDestructor(int client); - -/* 初始化/清理 */ -int ZhihuiNetOpenHarmonyInit(void); -int ZhihuiNetOpenHarmonyDeinit(void); - -#ifdef __cplusplus -} -#endif - -#endif /* ZHIHUI_NET_OPENHARMONY_ADAPTER_H */ - diff --git a/code/sdk/zhihui/zhihui_net/adapter/zhihui_net_ppp_adapter.c b/code/sdk/zhihui/zhihui_net/adapter/zhihui_net_ppp_adapter.c deleted file mode 100644 index bbdc372a9..000000000 --- a/code/sdk/zhihui/zhihui_net/adapter/zhihui_net_ppp_adapter.c +++ /dev/null @@ -1,445 +0,0 @@ -/** - * @file zhihui_net_ppp_adapter.c - * @brief N706A PPP网络实现适配器实现 - * - * 将统一接口转换为N706A PPP的接口 - */ - -#include "zhihui_net_ppp_adapter.h" -#include "n706a_http_client.h" -#include "n706a_websocket.h" -#include -#include -#include - -/* ========== HTTP接口适配 ========== */ - -int ZhihuiNetPppHttpGet(ZhihuiHttpClient *client, const char *url, - ZhihuiHttpClientData *data) -{ - n706a_http_client_t *ppp_client = (n706a_http_client_t *)client->impl_data; - - /* 如果还未初始化PPP客户端,则初始化 */ - if (ppp_client == NULL) { - ppp_client = (n706a_http_client_t *)malloc(sizeof(n706a_http_client_t)); - if (ppp_client == NULL) { - return -1; - } - - n706a_httpc_result_t ret = n706a_http_client_init(ppp_client, 0); - if (ret != N706A_HTTP_SUCCESS) { - free(ppp_client); - return -1; - } - - client->impl_data = ppp_client; - } - - /* 解析URL获取host和path */ - char host[256] = {0}; - char path[256] = {0}; - uint16_t port = 80; - bool use_ssl = false; - - if (strncmp(url, "https://", 8) == 0) { - use_ssl = true; - port = 443; - sscanf(url + 8, "%255[^/]%255s", host, path); - } else if (strncmp(url, "http://", 7) == 0) { - sscanf(url + 7, "%255[^/]%255s", host, path); - } else { - return -1; - } - - if (strlen(path) == 0) { - strcpy(path, "/"); - } - - /* 连接 */ - n706a_httpc_result_t ret = n706a_http_client_connect(ppp_client, host, port, use_ssl); - if (ret != N706A_HTTP_SUCCESS) { - return -1; - } - - /* 准备数据结构 */ - n706a_http_client_data_t ppp_data = {0}; - ppp_data.responseBuf = data->responseBuf; - ppp_data.responseBufLen = data->responseBufLen; - ppp_data.headerBuf = data->headerBuf; - ppp_data.headerBufLen = data->headerBufLen; - - /* 执行GET请求 */ - ret = n706a_http_client_get(ppp_client, path, &ppp_data); - - /* 设置响应码 */ - client->responseCode = ppp_client->response_code; - data->responseContentLen = ppp_data.responseContentLen; - - return (ret == N706A_HTTP_SUCCESS) ? 0 : -1; -} - -int ZhihuiNetPppHttpPost(ZhihuiHttpClient *client, const char *url, - ZhihuiHttpClientData *data) -{ - n706a_http_client_t *ppp_client = (n706a_http_client_t *)client->impl_data; - - if (ppp_client == NULL) { - ppp_client = (n706a_http_client_t *)malloc(sizeof(n706a_http_client_t)); - if (ppp_client == NULL) { - return -1; - } - - n706a_httpc_result_t ret = n706a_http_client_init(ppp_client, 0); - if (ret != N706A_HTTP_SUCCESS) { - free(ppp_client); - return -1; - } - - client->impl_data = ppp_client; - } - - /* 解析URL */ - char host[256] = {0}; - char path[256] = {0}; - uint16_t port = 80; - bool use_ssl = false; - - if (strncmp(url, "https://", 8) == 0) { - use_ssl = true; - port = 443; - sscanf(url + 8, "%255[^/]%255s", host, path); - } else if (strncmp(url, "http://", 7) == 0) { - sscanf(url + 7, "%255[^/]%255s", host, path); - } else { - return -1; - } - - if (strlen(path) == 0) { - strcpy(path, "/"); - } - - /* 连接 */ - n706a_httpc_result_t ret = n706a_http_client_connect(ppp_client, host, port, use_ssl); - if (ret != N706A_HTTP_SUCCESS) { - return -1; - } - - /* 准备数据结构 */ - n706a_http_client_data_t ppp_data = {0}; - ppp_data.responseBuf = data->responseBuf; - ppp_data.responseBufLen = data->responseBufLen; - ppp_data.headerBuf = data->headerBuf; - ppp_data.headerBufLen = data->headerBufLen; - - /* 执行POST请求 */ - const char *content_type = data->postContentType ? data->postContentType : "application/json"; - ret = n706a_http_client_post(ppp_client, path, content_type, - data->postBuf, data->postBufLen, &ppp_data); - - client->responseCode = ppp_client->response_code; - data->responseContentLen = ppp_data.responseContentLen; - - return (ret == N706A_HTTP_SUCCESS) ? 0 : -1; -} - -int ZhihuiNetPppHttpHead(ZhihuiHttpClient *client, const char *url, - ZhihuiHttpClientData *data) -{ - /* N706A暂不直接支持HEAD,使用GET实现 */ - return ZhihuiNetPppHttpGet(client, url, data); -} - -int ZhihuiNetPppHttpPut(ZhihuiHttpClient *client, const char *url, - ZhihuiHttpClientData *data) -{ - n706a_http_client_t *ppp_client = (n706a_http_client_t *)client->impl_data; - - if (ppp_client == NULL) { - ppp_client = (n706a_http_client_t *)malloc(sizeof(n706a_http_client_t)); - if (ppp_client == NULL) { - return -1; - } - - n706a_httpc_result_t ret = n706a_http_client_init(ppp_client, 0); - if (ret != N706A_HTTP_SUCCESS) { - free(ppp_client); - return -1; - } - - client->impl_data = ppp_client; - } - - /* 解析URL */ - char host[256] = {0}; - char path[256] = {0}; - uint16_t port = 80; - bool use_ssl = false; - - if (strncmp(url, "https://", 8) == 0) { - use_ssl = true; - port = 443; - sscanf(url + 8, "%255[^/]%255s", host, path); - } else if (strncmp(url, "http://", 7) == 0) { - sscanf(url + 7, "%255[^/]%255s", host, path); - } else { - return -1; - } - - if (strlen(path) == 0) { - strcpy(path, "/"); - } - - /* 连接 */ - n706a_httpc_result_t ret = n706a_http_client_connect(ppp_client, host, port, use_ssl); - if (ret != N706A_HTTP_SUCCESS) { - return -1; - } - - /* 准备数据结构 */ - n706a_http_client_data_t ppp_data = {0}; - ppp_data.responseBuf = data->responseBuf; - ppp_data.responseBufLen = data->responseBufLen; - ppp_data.headerBuf = data->headerBuf; - ppp_data.headerBufLen = data->headerBufLen; - - /* 执行PUT请求 */ - const char *content_type = data->postContentType ? data->postContentType : "application/json"; - ret = n706a_http_client_put(ppp_client, path, content_type, - data->postBuf, data->postBufLen, &ppp_data); - - client->responseCode = ppp_client->response_code; - data->responseContentLen = ppp_data.responseContentLen; - - return (ret == N706A_HTTP_SUCCESS) ? 0 : -1; -} - -int ZhihuiNetPppHttpDelete(ZhihuiHttpClient *client, const char *url, - ZhihuiHttpClientData *data) -{ - n706a_http_client_t *ppp_client = (n706a_http_client_t *)client->impl_data; - - if (ppp_client == NULL) { - ppp_client = (n706a_http_client_t *)malloc(sizeof(n706a_http_client_t)); - if (ppp_client == NULL) { - return -1; - } - - n706a_httpc_result_t ret = n706a_http_client_init(ppp_client, 0); - if (ret != N706A_HTTP_SUCCESS) { - free(ppp_client); - return -1; - } - - client->impl_data = ppp_client; - } - - /* 解析URL */ - char host[256] = {0}; - char path[256] = {0}; - uint16_t port = 80; - bool use_ssl = false; - - if (strncmp(url, "https://", 8) == 0) { - use_ssl = true; - port = 443; - sscanf(url + 8, "%255[^/]%255s", host, path); - } else if (strncmp(url, "http://", 7) == 0) { - sscanf(url + 7, "%255[^/]%255s", host, path); - } else { - return -1; - } - - if (strlen(path) == 0) { - strcpy(path, "/"); - } - - /* 连接 */ - n706a_httpc_result_t ret = n706a_http_client_connect(ppp_client, host, port, use_ssl); - if (ret != N706A_HTTP_SUCCESS) { - return -1; - } - - /* 准备数据结构 */ - n706a_http_client_data_t ppp_data = {0}; - ppp_data.responseBuf = data->responseBuf; - ppp_data.responseBufLen = data->responseBufLen; - ppp_data.headerBuf = data->headerBuf; - ppp_data.headerBufLen = data->headerBufLen; - - /* 执行DELETE请求 */ - ret = n706a_http_client_delete(ppp_client, path, &ppp_data); - - client->responseCode = ppp_client->response_code; - data->responseContentLen = ppp_data.responseContentLen; - - return (ret == N706A_HTTP_SUCCESS) ? 0 : -1; -} - -/* ========== WebSocket接口适配 ========== */ - -#define MAX_WS_CLIENTS 6 /* N706A支持最多6个并发连接 */ - -typedef struct { - int client_id; - bool in_use; - ZhihuiWebSocketOnOpenCallback zhihui_on_open; - ZhihuiWebSocketOnMessageCallback zhihui_on_message; - ZhihuiWebSocketOnErrorCallback zhihui_on_error; - ZhihuiWebSocketOnCloseCallback zhihui_on_close; -} ppp_ws_adapter_t; - -static ppp_ws_adapter_t g_ppp_ws_adapters[MAX_WS_CLIENTS]; - -/* PPP回调转发到Zhihui回调 */ -static void ppp_ws_on_open(int client_id) -{ - if (client_id >= 0 && client_id < MAX_WS_CLIENTS) { - if (g_ppp_ws_adapters[client_id].in_use && - g_ppp_ws_adapters[client_id].zhihui_on_open) { - g_ppp_ws_adapters[client_id].zhihui_on_open(client_id); - } - } -} - -static void ppp_ws_on_message(int client_id, const char *data, size_t length, - bool isFinished, bool isBinary) -{ - if (client_id >= 0 && client_id < MAX_WS_CLIENTS) { - if (g_ppp_ws_adapters[client_id].in_use && - g_ppp_ws_adapters[client_id].zhihui_on_message) { - g_ppp_ws_adapters[client_id].zhihui_on_message(client_id, data, - length, isFinished, isBinary); - } - } -} - -static void ppp_ws_on_error(int client_id, int errorCode, const char *errorMsg) -{ - if (client_id >= 0 && client_id < MAX_WS_CLIENTS) { - if (g_ppp_ws_adapters[client_id].in_use && - g_ppp_ws_adapters[client_id].zhihui_on_error) { - g_ppp_ws_adapters[client_id].zhihui_on_error(client_id, errorCode, errorMsg); - } - } -} - -static void ppp_ws_on_close(int client_id, int closeCode, const char *reason) -{ - if (client_id >= 0 && client_id < MAX_WS_CLIENTS) { - if (g_ppp_ws_adapters[client_id].in_use && - g_ppp_ws_adapters[client_id].zhihui_on_close) { - g_ppp_ws_adapters[client_id].zhihui_on_close(client_id, closeCode, reason); - } - } -} - -int ZhihuiNetPppWsConstructor(ZhihuiWebSocketOnOpenCallback onOpen, - ZhihuiWebSocketOnMessageCallback onMessage, - ZhihuiWebSocketOnErrorCallback onError, - ZhihuiWebSocketOnCloseCallback onClose) -{ - int client_id = n706a_websocket_constructor( - ppp_ws_on_open, - ppp_ws_on_message, - ppp_ws_on_error, - ppp_ws_on_close - ); - - if (client_id >= 0 && client_id < MAX_WS_CLIENTS) { - g_ppp_ws_adapters[client_id].client_id = client_id; - g_ppp_ws_adapters[client_id].in_use = true; - g_ppp_ws_adapters[client_id].zhihui_on_open = onOpen; - g_ppp_ws_adapters[client_id].zhihui_on_message = onMessage; - g_ppp_ws_adapters[client_id].zhihui_on_error = onError; - g_ppp_ws_adapters[client_id].zhihui_on_close = onClose; - } - - return client_id; -} - -int ZhihuiNetPppWsConnect(int client, const char *url, const char *option) -{ - if (client < 0 || client >= MAX_WS_CLIENTS || !g_ppp_ws_adapters[client].in_use) { - return -1; - } - - return n706a_websocket_connect(client, url, option); -} - -int ZhihuiNetPppWsSendText(int client, const char *text, size_t length) -{ - if (client < 0 || client >= MAX_WS_CLIENTS || !g_ppp_ws_adapters[client].in_use) { - return -1; - } - - return n706a_websocket_send_text(client, text, length); -} - -int ZhihuiNetPppWsSendBinStart(int client, const char *data, size_t length) -{ - if (client < 0 || client >= MAX_WS_CLIENTS || !g_ppp_ws_adapters[client].in_use) { - return -1; - } - - return n706a_websocket_send_binary(client, (const uint8_t *)data, length); -} - -int ZhihuiNetPppWsSendBinContinue(int client, const char *data, size_t length) -{ - /* N706A一次发送完整帧,不支持分段 */ - return 0; -} - -int ZhihuiNetPppWsSendBinEnd(int client, const char *data, size_t length) -{ - /* N706A一次发送完整帧,不支持分段 */ - return 0; -} - -int ZhihuiNetPppWsClose(int client) -{ - if (client < 0 || client >= MAX_WS_CLIENTS || !g_ppp_ws_adapters[client].in_use) { - return -1; - } - - return n706a_websocket_close(client); -} - -int ZhihuiNetPppWsDestructor(int client) -{ - if (client < 0 || client >= MAX_WS_CLIENTS || !g_ppp_ws_adapters[client].in_use) { - return -1; - } - - int ret = n706a_websocket_destructor(client); - - if (ret == 0) { - memset(&g_ppp_ws_adapters[client], 0, sizeof(ppp_ws_adapter_t)); - g_ppp_ws_adapters[client].in_use = false; - } - - return ret; -} - -/* ========== 初始化/清理 ========== */ - -int ZhihuiNetPppInit(void) -{ - printf("[ZhihuiNet][PPP] Adapter initialized\n"); - memset(g_ppp_ws_adapters, 0, sizeof(g_ppp_ws_adapters)); - return 0; -} - -int ZhihuiNetPppDeinit(void) -{ - printf("[ZhihuiNet][PPP] Adapter deinitialized\n"); - - /* 清理所有WebSocket客户端 */ - for (int i = 0; i < MAX_WS_CLIENTS; i++) { - if (g_ppp_ws_adapters[i].in_use) { - ZhihuiNetPppWsDestructor(i); - } - } - - return 0; -} diff --git a/code/sdk/zhihui/zhihui_net/adapter/zhihui_net_ppp_adapter.h b/code/sdk/zhihui/zhihui_net/adapter/zhihui_net_ppp_adapter.h deleted file mode 100644 index 08bf64f07..000000000 --- a/code/sdk/zhihui/zhihui_net/adapter/zhihui_net_ppp_adapter.h +++ /dev/null @@ -1,49 +0,0 @@ -/** - * @file zhihui_net_ppp_adapter.h - * @brief N706A PPP网络实现适配器 - */ - -#ifndef ZHIHUI_NET_PPP_ADAPTER_H -#define ZHIHUI_NET_PPP_ADAPTER_H - -#include "zhihui_net.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/* HTTP接口 */ -int ZhihuiNetPppHttpGet(ZhihuiHttpClient *client, const char *url, - ZhihuiHttpClientData *data); -int ZhihuiNetPppHttpPost(ZhihuiHttpClient *client, const char *url, - ZhihuiHttpClientData *data); -int ZhihuiNetPppHttpHead(ZhihuiHttpClient *client, const char *url, - ZhihuiHttpClientData *data); -int ZhihuiNetPppHttpPut(ZhihuiHttpClient *client, const char *url, - ZhihuiHttpClientData *data); -int ZhihuiNetPppHttpDelete(ZhihuiHttpClient *client, const char *url, - ZhihuiHttpClientData *data); - -/* WebSocket接口 */ -int ZhihuiNetPppWsConstructor(ZhihuiWebSocketOnOpenCallback onOpen, - ZhihuiWebSocketOnMessageCallback onMessage, - ZhihuiWebSocketOnErrorCallback onError, - ZhihuiWebSocketOnCloseCallback onClose); -int ZhihuiNetPppWsConnect(int client, const char *url, const char *option); -int ZhihuiNetPppWsSendText(int client, const char *text, size_t length); -int ZhihuiNetPppWsSendBinStart(int client, const char *data, size_t length); -int ZhihuiNetPppWsSendBinContinue(int client, const char *data, size_t length); -int ZhihuiNetPppWsSendBinEnd(int client, const char *data, size_t length); -int ZhihuiNetPppWsClose(int client); -int ZhihuiNetPppWsDestructor(int client); - -/* 初始化/清理 */ -int ZhihuiNetPppInit(void); -int ZhihuiNetPppDeinit(void); - -#ifdef __cplusplus -} -#endif - -#endif /* ZHIHUI_NET_PPP_ADAPTER_H */ - diff --git a/code/sdk/zhihui/zhihui_net/include/zhihui_net.h b/code/sdk/zhihui/zhihui_net/include/zhihui_net.h index c31f8712a..eced4554b 100644 --- a/code/sdk/zhihui/zhihui_net/include/zhihui_net.h +++ b/code/sdk/zhihui/zhihui_net/include/zhihui_net.h @@ -1,14 +1,22 @@ /** * @file zhihui_net.h * @brief 智慧网络统一接口层 - 总入口 - * + * * 设计原则: * 1. 拆分 HTTP 和 WebSocket 接口到独立的头文件 * 2. HTTP 接口:基于 OpenHarmony 的 httpclient.h * 3. WebSocket 接口:基于 haisi_websocket.h - * 4. 支持运行时动态切换实现(OpenHarmony/haisi_websocket vs PPP) - * - * @version 4.0.0 + * 4. 支持运行时动态切换实现(OpenHarmony/haisi_websocket vs N706A AT) + * + * 模式自动检测: + * - 定义 SUPPORT_N706A_NET → 使用 N706A AT 模式(不拨号,纯AT指令) + * - 未定义 SUPPORT_N706A_NET → 使用 OpenHarmony 模式(PPP拨号 + lwIP) + * + * SSL实现(仅影响N706A内部): + * - N706A_SSL_USE_MBEDTLS → n706a_net 内部使用 mbedtls 实现SSL + * - N706A_SSL_USE_AT_COMMAND → n706a_net 内部使用 AT 指令实现SSL + * + * @version 5.1.0 * @date 2025-10-31 */ @@ -30,13 +38,27 @@ extern "C" { /* ========== 网络实现类型 ========== */ typedef enum { - ZHIHUI_NET_IMPL_OPENHARMONY = 0, /**< OpenHarmony实现(PPP拨号 + lwIP + haisi_websocket)默认 */ + ZHIHUI_NET_IMPL_OPENHARMONY = 0, /**< OpenHarmony实现(PPP拨号 + lwIP + haisi_websocket)*/ ZHIHUI_NET_IMPL_N706A_AT = 1 /**< N706A AT指令实现(不拨号,纯AT指令)*/ } zhihui_net_impl_type_t; /* 为了兼容性保留旧名称 */ #define ZHIHUI_NET_IMPL_N706A_PPP ZHIHUI_NET_IMPL_OPENHARMONY +/* ========== 自动模式检测(基于 SUPPORT_N706A_NET 宏)========== */ +/** + * 根据 SUPPORT_N706A_NET 宏自动选择网络模式: + * - 定义了 SUPPORT_N706A_NET → N706A AT 模式(不拨号PPP) + * - 未定义 SUPPORT_N706A_NET → OpenHarmony 模式(PPP拨号 + lwIP) + */ +#ifdef SUPPORT_N706A_NET + #define ZHIHUI_NET_DEFAULT_MODE ZHIHUI_NET_IMPL_N706A_AT + #define ZHIHUI_NET_MODE_DESC "N706A AT Command (no PPP dial)" +#else + #define ZHIHUI_NET_DEFAULT_MODE ZHIHUI_NET_IMPL_OPENHARMONY + #define ZHIHUI_NET_MODE_DESC "OpenHarmony (PPP + lwIP)" +#endif + /* ========== 配置接口 ========== */ /** diff --git a/code/sdk/zhihui/zhihui_net/src/zhihui_net.c b/code/sdk/zhihui/zhihui_net/src/zhihui_net.c index 4d973a9ea..b20db5598 100644 --- a/code/sdk/zhihui/zhihui_net/src/zhihui_net.c +++ b/code/sdk/zhihui/zhihui_net/src/zhihui_net.c @@ -3,6 +3,9 @@ * @brief 智慧网络统一接口层 - 配置实现 * * 负责管理网络实现类型的配置和切换 + * 根据 SUPPORT_N706A_NET 宏自动选择默认模式: + * - 定义了 SUPPORT_N706A_NET → N706A AT 模式(不拨号PPP) + * - 未定义 SUPPORT_N706A_NET → OpenHarmony 模式(PPP拨号 + lwIP) */ #include "zhihui_net.h" @@ -10,8 +13,8 @@ #include #include -/* 当前使用的实现类型 */ -static zhihui_net_impl_type_t g_current_impl = ZHIHUI_NET_IMPL_OPENHARMONY; +/* 当前使用的实现类型(自动根据 SUPPORT_N706A_NET 宏设置默认值) */ +static zhihui_net_impl_type_t g_current_impl = ZHIHUI_NET_DEFAULT_MODE; /* ========== 配置接口实现 ========== */ @@ -34,9 +37,13 @@ zhihui_net_impl_type_t ZhihuiNetGetImplementation(void) int ZhihuiNetInit(void) { - /* 初始化网络模块 */ - printf("[ZhihuiNet] Initialized with %s implementation\n", - (g_current_impl == ZHIHUI_NET_IMPL_OPENHARMONY) ? "OpenHarmony" : "PPP"); + /* 初始化网络模块,显示当前模式 */ + const char *mode_str = (g_current_impl == ZHIHUI_NET_IMPL_OPENHARMONY) ? + "OpenHarmony (PPP + lwIP)" : "N706A AT Command"; + + printf("[ZhihuiNet] Initialized with %s implementation\n", mode_str); + printf("[ZhihuiNet] Auto-detected mode: %s\n", ZHIHUI_NET_MODE_DESC); + return 0; }