添加ppp开机时间同步
author孙春阳 <snickersun@163.com>
Mon, 20 Oct 2025 03:41:03 +0000 (11:41 +0800)
committer孙春阳 <snickersun@163.com>
Mon, 20 Oct 2025 03:41:03 +0000 (11:41 +0800)
code/sdk/zhihui/ppp/include/ppp_api.h
code/sdk/zhihui/ppp/src/ppp_api.c

index 8fcbbdd..7e5ba60 100644 (file)
@@ -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 设置默认网卡
index ed74494..c660089 100644 (file)
@@ -10,6 +10,8 @@
 #include "soc_osal.h"
 #include <string.h>
 #include <stdlib.h>          // 添加malloc/free支持
+#include <time.h>            // 添加时间处理支持
+#include <sys/time.h>        // 添加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(&current_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 错误码