From: 孙春阳 Date: Mon, 20 Oct 2025 03:41:03 +0000 (+0800) Subject: 添加ppp开机时间同步 X-Git-Tag: V1.0.9_20251128_194606_LN~203^2~1 X-Git-Url: http://git.snicker.com.cn/?a=commitdiff_plain;h=4ae205266fa8b967f7eb06a752c1fcc14816bd65;p=mirrors%2FFD230.git 添加ppp开机时间同步 --- diff --git a/code/sdk/zhihui/ppp/include/ppp_api.h b/code/sdk/zhihui/ppp/include/ppp_api.h index 8fcbbdd18..7e5ba60ba 100644 --- a/code/sdk/zhihui/ppp/include/ppp_api.h +++ b/code/sdk/zhihui/ppp/include/ppp_api.h @@ -180,6 +180,17 @@ errcode_t ppp_api_update_all_runtime_status(void); * @return ERRCODE_SUCC-成功,其他-失败 */ errcode_t ppp_api_update_netinfo_in_all_mode(void); + +/** + * @brief 通过AT+CCLK命令获取网络时间并更新系统时间 + * @return ERRCODE_SUCC-成功,其他-失败 + * + * 说明: + * - 必须在AT模式下调用 + * - 自动解析AT+CCLK响应并设置系统时间 + * - 支持时区自动转换 + */ +errcode_t ppp_api_update_time(void); /** * @brief 设置默认网卡 diff --git a/code/sdk/zhihui/ppp/src/ppp_api.c b/code/sdk/zhihui/ppp/src/ppp_api.c index ed74494cd..c660089de 100644 --- a/code/sdk/zhihui/ppp/src/ppp_api.c +++ b/code/sdk/zhihui/ppp/src/ppp_api.c @@ -10,6 +10,8 @@ #include "soc_osal.h" #include #include // 添加malloc/free支持 +#include // 添加时间处理支持 +#include // 添加settimeofday支持 #include "osal_debug.h" #include "schedule/osal_task.h" #include "securec.h" @@ -40,6 +42,11 @@ uint8_t g_ppp_enable_debug = PPP_LOG_LEVEL_INFO; extern void cat1_api_enable_cat1(bool isopen); static void ppp_check_and_update_state(void); +// 时区相关宏定义(参照 cat1_porting.c) +#define MINUTES_PER_QUARTER 15 /* minutes per quarter */ +#define QUARTERS_PER_HOUR 4 /* quarters per hour */ +#define MAX_TIMEZONE_HOURS 24 + errcode_t ppp_api_get_ppp_netif_name(char* netif_name, uint32_t buffer_size); void ppp_check_at_communication_and_baudrate(void); @@ -1810,6 +1817,12 @@ errcode_t ppp_api_update_all_runtime_status(void) PPP_DEBUG_ERROR("Failed to update version"); ret = ERRCODE_FAIL; } + + // 更新时间 + if (ppp_api_update_time() != ERRCODE_SUCC) { + PPP_DEBUG_ERROR("Failed to update time"); + ret = ERRCODE_FAIL; + } PPP_DEBUG_INFO("Runtime status update completed"); return ret; @@ -2749,6 +2762,304 @@ static void ppp_check_and_update_state(void) } } +/** + * @brief 解析AT+CCLK响应并转换为时间戳和时区 + * @param response AT命令响应 + * @param timestamp 输出UTC时间戳(秒) + * @param timezone_quarters 输出时区(quarters,15分钟为单位) + * @return ERRCODE_SUCC-成功,其他-失败 + * + * AT+CCLK响应格式示例: + * +CCLK: "25/10/20,02:59:43+32" + * + * 重要:CCLK返回的时间字段是UTC时间,不是本地时间! + * - 时间字段:02:59:43 是 UTC 时间 + * - 时区字段:+32 表示本地时区为 UTC+8(32 quarters = 8 hours) + * - 本地时间:02:59:43 + 8小时 = 10:59:43 + * + * 处理流程: + * 1. 解析CCLK响应得到UTC时间和时区偏移 + * 2. 将UTC时间转换为时间戳 + * 3. 返回UTC时间戳和时区信息 + */ +static errcode_t ppp_parse_cclk_and_convert_to_timestamp(const char *response, + uint32_t *timestamp, + int *timezone_quarters) +{ + if (!response || !timestamp || !timezone_quarters) { + return ERRCODE_INVALID_PARAM; + } + + // 查找 "+CCLK: " + const char *start = strstr(response, "+CCLK: "); + if (start == NULL) { + PPP_DEBUG_ERROR("CCLK response format error: no +CCLK: prefix"); + return ERRCODE_FAIL; + } + + start += 7; // 跳过"+CCLK: " + + // 查找第一个引号 + const char *quote_start = strchr(start, '"'); + if (quote_start == NULL) { + PPP_DEBUG_ERROR("CCLK response format error: no opening quote"); + return ERRCODE_FAIL; + } + quote_start++; // 跳过引号 + + // 查找第二个引号 + const char *quote_end = strchr(quote_start, '"'); + if (quote_end == NULL) { + PPP_DEBUG_ERROR("CCLK response format error: no closing quote"); + return ERRCODE_FAIL; + } + + // 提取并解析时间字符串 + char time_str[32] = {0}; + size_t len = quote_end - quote_start; + if (len >= sizeof(time_str)) { + len = sizeof(time_str) - 1; + } + strncpy_s(time_str, sizeof(time_str), quote_start, len); + time_str[len] = '\0'; + + PPP_DEBUG_INFO("Parsing CCLK time: %s", time_str); + + // 解析CCLK格式: "YY/MM/DD,HH:MM:SS+TZ" + int year, month, day, hour, minute, second, tz_quarters; + int parsed = sscanf_s(time_str, "%d/%d/%d,%d:%d:%d%d", + &year, &month, &day, &hour, &minute, &second, &tz_quarters); + + if (parsed != 7) { + PPP_DEBUG_ERROR("Failed to parse CCLK time format: %s", time_str); + return ERRCODE_FAIL; + } + + // 转换2位年份为4位年份(假设20xx年) + if (year < 100) { + year += 2000; + } + + // 验证年份合法性(参照 cat1_utils.c 中的检查) + if (year < 2023) { + PPP_DEBUG_ERROR("Invalid year: %d (must be >= 2023)", year); + return ERRCODE_FAIL; + } + + // 构造 struct tm 结构(CCLK返回的是UTC时间) + struct tm time_info = {0}; + time_info.tm_year = year - 1900; // tm_year 是从1900年开始的 + time_info.tm_mon = month - 1; // tm_mon 是 0-11 + time_info.tm_mday = day; + time_info.tm_hour = hour; + time_info.tm_min = minute; + time_info.tm_sec = second; + time_info.tm_isdst = -1; // 让系统自动判断夏令时 + + // 使用 mktime 转换为时间戳 + // 注意:CCLK返回的时间字段是UTC时间,不是本地时间 + time_t _timestamp = mktime(&time_info); + if (_timestamp == -1) { + PPP_DEBUG_ERROR("Failed to convert time to timestamp"); + return ERRCODE_FAIL; + } + + // CCLK返回的时间本身就是UTC时间,直接使用 + // 时区字段表示本地时区相对于UTC的偏移 + uint32_t utc_timestamp = (uint32_t)_timestamp; + + *timestamp = utc_timestamp; + *timezone_quarters = tz_quarters; + + // 计算本地时间用于显示验证(精确计算,包括非整数小时) + int timezone_hours = tz_quarters / QUARTERS_PER_HOUR; // 小时部分 + int timezone_minutes = (tz_quarters % QUARTERS_PER_HOUR) * MINUTES_PER_QUARTER; // 分钟部分 + + // 计算本地时间:UTC + 时区偏移 + int local_hour = hour; + int local_minute = minute + timezone_minutes; + int local_day = day; + + // 处理分钟进位 + if (local_minute >= 60) { + local_hour += local_minute / 60; + local_minute = local_minute % 60; + } else if (local_minute < 0) { + local_hour += (local_minute / 60) - 1; + local_minute = 60 + (local_minute % 60); + } + + // 加上时区的小时部分 + local_hour += timezone_hours; + + // 处理小时进位 + if (local_hour >= 24) { + local_day += local_hour / 24; + local_hour = local_hour % 24; + } else if (local_hour < 0) { + local_day += (local_hour / 24) - 1; + local_hour = 24 + (local_hour % 24); + } + + PPP_DEBUG_WARN("Parsed UTC time: %04d-%02d-%02d %02d:%02d:%02d", + year, month, day, hour, minute, second); + + // 显示时区信息(支持非整数小时) + if (timezone_minutes == 0) { + PPP_DEBUG_WARN("Timezone: %+d quarters (UTC%+d)", + tz_quarters, timezone_hours); + } else { + PPP_DEBUG_WARN("Timezone: %+d quarters (UTC%+d:%02d)", + tz_quarters, timezone_hours, timezone_minutes); + } + + PPP_DEBUG_WARN("Local time will be: %04d-%02d-%02d %02d:%02d:%02d", + year, month, local_day, local_hour, local_minute, second); + PPP_DEBUG_WARN("UTC timestamp: %u", utc_timestamp); + + return ERRCODE_SUCC; +} + +/** + * @brief 设置系统时间(参照 cat1_porting.c 的 sync_time_utc 实现) + * @param utc_timestamp UTC时间戳(秒) + * @param timezone_quarters 时区(quarters,15分钟为单位) + * @return ERRCODE_SUCC-成功,其他-失败 + * + * 时间设置逻辑说明: + * - 输入:UTC时间戳 + 时区偏移 + * - 设置:本地时间 = UTC时间 + 时区偏移 + * + * 示例(UTC+8): + * - UTC时间:02:59:43 + * - 时区:+32 quarters = +8 hours + * - 本地时间:02:59:43 + 8小时 = 10:59:43 + * - settimeofday 会将系统时间设置为 10:59:43 + */ +static errcode_t ppp_set_system_time(uint32_t utc_timestamp, int timezone_quarters) +{ + // 参数验证(参照 cat1_porting.c) + if (timezone_quarters > (MAX_TIMEZONE_HOURS * QUARTERS_PER_HOUR) || + timezone_quarters < -(MAX_TIMEZONE_HOURS * QUARTERS_PER_HOUR)) { + PPP_DEBUG_ERROR("Invalid timezone: %d quarters", timezone_quarters); + return ERRCODE_FAIL; + } + + if (utc_timestamp == 0) { + PPP_DEBUG_ERROR("Invalid timestamp: 0"); + return ERRCODE_FAIL; + } + + // 准备时间结构(参照 cat1_porting.c 的实现) + struct timeval tv = {0}; + struct timezone tzone = {0}; + + // 设置本地时间 = UTC时间 + 时区偏移 + // settimeofday 的 tv_sec 应该设置为本地时间戳 + tv.tv_sec = utc_timestamp + timezone_quarters * MINUTES_PER_QUARTER * 60; + tv.tv_usec = 0; + + // 设置时区信息 + tzone.tz_dsttime = 0; // 不使用夏令时 + tzone.tz_minuteswest = timezone_quarters * MINUTES_PER_QUARTER; + + PPP_DEBUG_INFO("Setting system time:"); + PPP_DEBUG_INFO(" UTC timestamp: %u", utc_timestamp); + PPP_DEBUG_INFO(" Timezone: %+d minutes (%+d quarters)", + tzone.tz_minuteswest, timezone_quarters); + PPP_DEBUG_INFO(" Local timestamp: %ld", tv.tv_sec); + + // 调用系统API设置时间 + int ret = settimeofday(&tv, &tzone); + if (ret != 0) { + PPP_DEBUG_ERROR("settimeofday failed: %d", ret); + return ERRCODE_FAIL; + } + + // 验证时间设置成功 + time_t current_time = time(NULL); + struct tm timeinfo; + localtime_r(¤t_time, &timeinfo); + + PPP_DEBUG_CRITICAL("System time set successfully: %04d-%02d-%02d %02d:%02d:%02d", + timeinfo.tm_year + 1900, + timeinfo.tm_mon + 1, + timeinfo.tm_mday, + timeinfo.tm_hour, + timeinfo.tm_min, + timeinfo.tm_sec); + + return ERRCODE_SUCC; +} + +/** + * @brief 通过AT+CCLK命令获取网络时间并更新系统时间 + * @return ERRCODE_SUCC-成功,其他-失败 + * + * 实现说明(参照 cat1_set_time_from_cat1): + * 1. 发送 AT+CCLK? 命令获取网络时间 + * 2. 解析响应,获取UTC时间和时区偏移 + * 3. 计算本地时间 = UTC时间 + 时区偏移 + * 4. 使用 settimeofday 设置系统时间为本地时间 + * + * AT+CCLK 返回格式说明: + * - 命令:AT+CCLK? + * - 响应:+CCLK: "25/10/20,02:59:43+32" + * - 时间字段(02:59:43)是 UTC 时间 + * - 时区字段(+32)表示本地时区为 UTC+8 + * - 最终显示时间:02:59:43 + 8小时 = 10:59:43 + */ +errcode_t ppp_api_update_time(void) +{ + // 检查是否在AT模式 + if (ppp_serial_get_mode() != PPP_SERIAL_MODE_AT || !get_at_communication_and_baudrate_flag()) { + PPP_DEBUG_WARN("Cannot update time: not in AT mode (current mode: %d)", ppp_serial_get_mode()); + return ERRCODE_FAIL; + } + + PPP_DEBUG_INFO("=== Starting network time synchronization ==="); + PPP_DEBUG_INFO("Sending AT+CCLK? command..."); + + // 分配响应缓冲区 + char *response = (char *)malloc(512); + if (response == NULL) { + PPP_DEBUG_ERROR("Failed to allocate memory for AT response"); + return ERRCODE_FAIL; + } + + // 发送AT+CCLK?命令 + uint32_t response_len; + errcode_t ret = ppp_execute_at_command("AT+CCLK?", response, 512, &response_len, 5000); + if (ret != ERRCODE_SUCC) { + PPP_DEBUG_ERROR("Failed to send AT+CCLK command"); + free(response); + return ret; + } + + PPP_DEBUG_INFO("AT+CCLK response received (%u bytes)", response_len); + + // 解析CCLK响应并转换为时间戳 + uint32_t utc_timestamp = 0; + int timezone_quarters = 0; + ret = ppp_parse_cclk_and_convert_to_timestamp(response, &utc_timestamp, &timezone_quarters); + free(response); + + if (ret != ERRCODE_SUCC) { + PPP_DEBUG_ERROR("Failed to parse CCLK response"); + return ret; + } + + // 设置系统时间 + ret = ppp_set_system_time(utc_timestamp, timezone_quarters); + if (ret != ERRCODE_SUCC) { + PPP_DEBUG_ERROR("Failed to set system time"); + return ret; + } + + PPP_DEBUG_CRITICAL("=== Network time synchronized successfully ==="); + return ERRCODE_SUCC; +} + /** * @brief 获取并显示网络信号信息 * @return 错误码