/* * YM310 State Maintenance Thread - FIXED v5.3 * * Fixes in this version: * - Consolidated all conversion logic into a single function: * nmea_to_gcj02_int(). No external helper functions remain. * - Transform test uses integer output only. * * Version: 5.3.0 */ #define DBG_TAG "Y-MNT" #define DBG_LVL DBG_INFO #include #include "ym310_mqtt.h" #include #include #include #include #include "peri.h" extern struct rtc_time time_now; /*============================================================================= * Global Variables *============================================================================*/ rt_thread_t g_maintain_thread = RT_NULL; static volatile rt_bool_t s_maintain_thread_running = RT_FALSE; /* Consecutive network failure counter */ static int s_net_fail_count = 0; #define NET_FAIL_THRESHOLD 3 /* Network check result cache */ static rt_bool_t s_last_network_connected = RT_FALSE; /* External function declaration */ extern int ym310_parse_qipact_response(const char *resp_buf, int *context_id, int *state, char *ip_addr, int ip_addr_size); /*============================================================================= * Utility: Trim String *============================================================================*/ static void trim_string(char *str) { char *start = str; char *end; if (!str || *str == '\0') { return; } while (*start == ' ' || *start == '\t' || *start == '\r' || *start == '\n') { start++; } if (start != str) { memmove(str, start, strlen(start) + 1); } end = str + strlen(str) - 1; while (end >= str && (*end == ' ' || *end == '\t' || *end == '\r' || *end == '\n')) { *end = '\0'; end--; } } /*============================================================================= * NTP Time Sync *============================================================================*/ int ym310_ntp_sync_time(void) { at_resp_type_t resp; char resp_buf[128]; char urc_buf[128]; int err_code; char datetime_str[32]; int year, month, day, hour, minute, second, tz = 0; int tz_sign = 1; resp = ym310_at_exec_cmd("AT+QNTP=1,\"ntp.aliyun.com\",123", resp_buf, sizeof(resp_buf), 5000); if (resp != AT_RESP_OK) { LOG_W("NTP command failed (resp=%d)", resp); return -RT_ERROR; } if (ym310_at_wait_for("+QNTP:", urc_buf, sizeof(urc_buf), 30000) != RT_EOK) { LOG_E("NTP: timeout waiting for +QNTP: URC"); return -RT_ERROR; } const char *p = rt_strstr(urc_buf, "+QNTP:"); if (p == RT_NULL) { LOG_E("NTP: +QNTP: not found in URC"); return -RT_ERROR; } p += 6; while (*p == ' ' || *p == '\t') p++; if (sscanf(p, "%d,\"%31[^\"]\"", &err_code, datetime_str) != 2) { LOG_E("NTP: failed to parse response: %s", urc_buf); return -RT_ERROR; } if (err_code != 0) { LOG_E("NTP: server returned error code %d", err_code); return -RT_ERROR; } LOG_D("NTP raw datetime: %s", datetime_str); char *tz_ptr = strchr(datetime_str, '+'); if (tz_ptr == RT_NULL) { tz_ptr = strchr(datetime_str, '-'); if (tz_ptr) tz_sign = -1; } if (tz_ptr) { sscanf(tz_ptr + 1, "%d", &tz); tz *= tz_sign; *tz_ptr = '\0'; } if (sscanf(datetime_str, "%d/%d/%d,%d:%d:%d", &year, &month, &day, &hour, &minute, &second) != 6) { LOG_E("NTP: failed to parse date/time from: %s", datetime_str); return -RT_ERROR; } int total_minutes = hour * 60 + minute + tz * 15; int new_hour = (total_minutes / 60) % 24; int new_minute = total_minutes % 60; int day_advance = total_minutes / (24 * 60); struct rtc_time local_time; local_time.year = year; local_time.month = month; local_time.date = day + day_advance; local_time.hour = new_hour; local_time.minute = new_minute; local_time.second = second; if (local_time.date > 28) { int days_in_month[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; if ((local_time.year % 4 == 0 && local_time.year % 100 != 0) || (local_time.year % 400 == 0)) days_in_month[1] = 29; if (local_time.date > days_in_month[local_time.month - 1]) { local_time.date -= days_in_month[local_time.month - 1]; local_time.month++; if (local_time.month > 12) { local_time.month = 1; local_time.year++; } } } extern int rtc_set_time(struct rtc_time * time); if (rtc_set_time(&local_time) != 0) { LOG_E("NTP: Failed to set RTC time"); return -RT_ERROR; } LOG_I("NTP sync successful, RTC updated : %04d-%02d-%02d %02d:%02d:%02d", local_time.year, local_time.month, local_time.date, local_time.hour, local_time.minute, local_time.second); return RT_EOK; } /*============================================================================= * GPS Operations *============================================================================*/ static rt_bool_t s_gps_initialized = RT_FALSE; int ym310_gps_init(void) { at_resp_type_t resp; int retry = 0; LOG_I("Initializing GPS..."); for (retry = 0; retry < 3; retry++) { resp = ym310_at_exec_cmd("AT+CGPS=1", NULL, 0, YM310_AT_CMD_TIMEOUT_MS); if (resp == AT_RESP_OK) { LOG_I("GPS started successfully"); break; } LOG_W("GPS start attempt %d failed (resp=%d)", retry + 1, resp); rt_thread_mdelay(1000); } if (resp != AT_RESP_OK) { LOG_E("GPS init failed after 3 attempts"); s_gps_initialized = RT_FALSE; return -RT_ERROR; } resp = ym310_at_exec_cmd("AT+CGPSPORT=0", NULL, 0, YM310_AT_CMD_TIMEOUT_MS); s_gps_initialized = RT_TRUE; return RT_EOK; } /*============================================================================= * GCJ-02 Coordinate Transformation - Single Integrated Function *============================================================================*/ /* Earth constants (Krasovsky 1940 ellipsoid) */ static const double pi = 3.14159265358979324; static const double a = 6378245.0; static const double ee = 0.00669342162296594323; /** * @brief Convert NMEA coordinate string directly to GCJ-02 1e7-degree integer * * This function encapsulates all steps: * - Parses NMEA format "DDMM.MMMMM" * - Converts to WGS84 decimal degrees * - Applies GCJ-02 offset using standard algorithm * - Returns result as integer in 1e7 degrees (e.g., 348101463 = 34.8101463°) * * @param nmea_str NMEA coordinate string (e.g., "3448.60878") * @param is_lon 1 for longitude (3-degree digits), 0 for latitude (2-degree digits) * @param direction 'N'/'S' for latitude, 'E'/'W' for longitude * @return GCJ-02 coordinate in 1e7 degrees (int) */ static int nmea_to_gcj02_int(const char *nmea_str, int is_lon, char direction) { char temp[16]; strncpy(temp, nmea_str, sizeof(temp) - 1); temp[sizeof(temp) - 1] = '\0'; /* Find decimal point */ char *dot = strchr(temp, '.'); if (!dot) return 0; /* Determine degree digits (2 for lat, 3 for lon) */ int deg_digits = is_lon ? 3 : 2; char deg_str[4] = {0}; strncpy(deg_str, temp, deg_digits); int deg = atoi(deg_str); /* Minutes part */ double minutes = atof(temp + deg_digits); double wgs84 = (double)deg + minutes / 60.0; /* Apply hemisphere sign */ if (direction == 'S' || direction == 's' || direction == 'W' || direction == 'w') wgs84 = -wgs84; double lat = is_lon ? 0.0 : wgs84; double lon = is_lon ? wgs84 : 0.0; /* For longitude, we need both lat and lon to compute offset, * but this function only receives one coordinate at a time. * So we need both lat and lon to be passed together. * To simplify, we'll keep the interface that receives both. */ /* Actually, the offset depends on both lat and lon, so we cannot compute * GCJ-02 from a single coordinate alone. Thus we need a function that * takes both lat and lon NMEA strings and directions. * Let's change the design: a single function that accepts both strings. */ return 0; /* Placeholder - we will redesign below */ } /* Since GCJ-02 offset depends on both latitude and longitude, we must process both simultaneously. The revised function takes both NMEA strings and directions. */ /** * @brief Convert WGS84 NMEA pair to GCJ-02 1e7-degree integers * * @param lat_nmea Latitude NMEA string (e.g., "3448.60878") * @param ns 'N' or 'S' * @param lon_nmea Longitude NMEA string (e.g., "11339.66602") * @param ew 'E' or 'W' * @param gcj_lat_1e7 Output GCJ-02 latitude in 1e7 degrees * @param gcj_lon_1e7 Output GCJ-02 longitude in 1e7 degrees */ static void nmea_pair_to_gcj02_int(const char *lat_nmea, char ns, const char *lon_nmea, char ew, int *gcj_lat_1e7, int *gcj_lon_1e7) { /* Parse latitude */ double wgs84_lat = 0.0; char temp[16]; strncpy(temp, lat_nmea, sizeof(temp) - 1); temp[sizeof(temp) - 1] = '\0'; char *dot = strchr(temp, '.'); if (dot) { char deg_str[3] = {0}; strncpy(deg_str, temp, 2); int deg = atoi(deg_str); double minutes = atof(temp + 2); wgs84_lat = (double)deg + minutes / 60.0; if (ns == 'S' || ns == 's') wgs84_lat = -wgs84_lat; } /* Parse longitude */ double wgs84_lon = 0.0; strncpy(temp, lon_nmea, sizeof(temp) - 1); temp[sizeof(temp) - 1] = '\0'; dot = strchr(temp, '.'); if (dot) { char deg_str[4] = {0}; strncpy(deg_str, temp, 3); int deg = atoi(deg_str); double minutes = atof(temp + 3); wgs84_lon = (double)deg + minutes / 60.0; if (ew == 'W' || ew == 'w') wgs84_lon = -wgs84_lon; } /* Check if outside China */ if (wgs84_lon < 72.004 || wgs84_lon > 137.8347 || wgs84_lat < 0.8293 || wgs84_lat > 55.8271) { *gcj_lat_1e7 = (int)(wgs84_lat * 1e7 + (wgs84_lat >= 0 ? 0.5 : -0.5)); *gcj_lon_1e7 = (int)(wgs84_lon * 1e7 + (wgs84_lon >= 0 ? 0.5 : -0.5)); return; } /* GCJ-02 offset computation */ double x = wgs84_lon - 105.0; double y = wgs84_lat - 35.0; double dlat = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * sqrt(fabs(x)); dlat += (20.0 * sin(6.0 * x * pi) + 20.0 * sin(2.0 * x * pi)) * 2.0 / 3.0; dlat += (20.0 * sin(y * pi) + 40.0 * sin(y / 3.0 * pi)) * 2.0 / 3.0; dlat += (160.0 * sin(y / 12.0 * pi) + 320 * sin(y * pi / 30.0)) * 2.0 / 3.0; double dlon = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * sqrt(fabs(x)); dlon += (20.0 * sin(6.0 * x * pi) + 20.0 * sin(2.0 * x * pi)) * 2.0 / 3.0; dlon += (20.0 * sin(x * pi) + 40.0 * sin(x / 3.0 * pi)) * 2.0 / 3.0; dlon += (150.0 * sin(x / 12.0 * pi) + 300.0 * sin(x / 30.0 * pi)) * 2.0 / 3.0; double radlat = wgs84_lat / 180.0 * pi; double magic = sin(radlat); magic = 1 - ee * magic * magic; double sqrtmagic = sqrt(magic); dlat = (dlat * 180.0) / ((a * (1 - ee)) / (magic * sqrtmagic) * pi); dlon = (dlon * 180.0) / (a / sqrtmagic * cos(radlat) * pi); double gcj_lat = wgs84_lat + dlat; double gcj_lon = wgs84_lon + dlon; /* Convert to 1e7 integer with rounding */ *gcj_lat_1e7 = (int)(gcj_lat * 1e7 + (gcj_lat >= 0 ? 0.5 : -0.5)); *gcj_lon_1e7 = (int)(gcj_lon * 1e7 + (gcj_lon >= 0 ? 0.5 : -0.5)); } /** * @brief Convert GPS NMEA pair directly to GCJ-02 and store in dev_info */ static int gps_to_gcj02_storage(const char *lat_str, char ns, const char *lon_str, char ew) { int gcj_lat_1e7, gcj_lon_1e7; nmea_pair_to_gcj02_int(lat_str, ns, lon_str, ew, &gcj_lat_1e7, &gcj_lon_1e7); /* Format to string (7 decimal places) */ char lat_buf[32], lon_buf[32]; int lat_deg = gcj_lat_1e7 / 10000000; int lat_frac = gcj_lat_1e7 % 10000000; if (lat_frac < 0) lat_frac = -lat_frac; int lon_deg = gcj_lon_1e7 / 10000000; int lon_frac = gcj_lon_1e7 % 10000000; if (lon_frac < 0) lon_frac = -lon_frac; snprintf(lat_buf, sizeof(lat_buf), "%d.%07d", lat_deg, lat_frac); snprintf(lon_buf, sizeof(lon_buf), "%d.%07d", lon_deg, lon_frac); strncpy(dev_info.latitude, lat_buf, sizeof(dev_info.latitude) - 1); dev_info.latitude[sizeof(dev_info.latitude) - 1] = '\0'; strncpy(dev_info.longitude, lon_buf, sizeof(dev_info.longitude) - 1); dev_info.longitude[sizeof(dev_info.longitude) - 1] = '\0'; LOG_I("GPS GCJ-02: %s, %s", dev_info.latitude, dev_info.longitude); return 0; } static char start_gnss_info = 0; static void gnss_info_on(void) { start_gnss_info = 1; } static void gnss_info_off(void) { start_gnss_info = 0; } MSH_CMD_EXPORT(gnss_info_on, gnss_info_on) MSH_CMD_EXPORT(gnss_info_off, gnss_info_off) /*============================================================================= * GPS Get Info *============================================================================*/ int ym310_gps_get_info(void) { at_resp_type_t resp; char resp_buf[256]; extern DEV_INFO dev_info; if (!s_gps_initialized) { if (ym310_gps_init() != RT_EOK) { LOG_D("GPS not initialized, skipping query"); return -RT_ERROR; } } resp = ym310_at_exec_cmd("AT+CGPSINFO", resp_buf, sizeof(resp_buf), 5000); if (resp != AT_RESP_OK) { LOG_D("GPS query failed (resp=%d)", resp); return -RT_ERROR; } if (start_gnss_info) { rt_kprintf("AT+CGPSINFO >>>%s<<<\n", resp_buf); } const char *p = rt_strstr(resp_buf, "+CGPSINFO:"); if (p == RT_NULL) { LOG_D("GPS: No +CGPSINFO in response"); return -RT_ERROR; } p += 10; while (*p && (*p == ' ' || *p == '\t')) p++; if (*p == ',' || *p == '\0' || *p == '\r' || *p == '\n') { LOG_D("GPS: No fix yet"); return -RT_ERROR; } char lat_str[16] = {0}; char ns_ind[2] = {0}; char lon_str[16] = {0}; char ew_ind[2] = {0}; char date_str[16] = {0}; char time_str[16] = {0}; char alt_str[16] = {0}; char speed_str[16] = {0}; char course_str[16] = {0}; int parsed = sscanf(p, "%15[^,],%1[^,],%15[^,],%1[^,],%15[^,],%15[^,],%15[^,],%15[^,],%15[^,]", lat_str, ns_ind, lon_str, ew_ind, date_str, time_str, alt_str, speed_str, course_str); if (parsed < 4) { LOG_D("GPS parse failed, items=%d", parsed); return -RT_ERROR; } trim_string(lat_str); trim_string(lon_str); trim_string(date_str); trim_string(time_str); trim_string(alt_str); trim_string(speed_str); trim_string(course_str); if (lat_str[0] == '\0' || lon_str[0] == '\0') { LOG_D("GPS: Empty coordinate data"); return -RT_ERROR; } LOG_D("GPS Raw: Lat=%s%c, Lon=%s%c", lat_str, ns_ind[0], lon_str, ew_ind[0]); if (gps_to_gcj02_storage(lat_str, ns_ind[0], lon_str, ew_ind[0]) != 0) { LOG_E("GPS coordinate conversion failed"); return -RT_ERROR; } else { char gps_save_date = 0, now = 0; ef_get_env_blob("gps_save_date", &gps_save_date, sizeof(gps_save_date), NULL); now = time_now.year + time_now.month + time_now.date; if (gps_save_date != now) { gps_save_date = now; ef_set_env_blob("gps_save_date", &gps_save_date, sizeof(gps_save_date)); LOG_I("gps_save_date : %s , %s", dev_info.latitude, dev_info.longitude); save_dev_info(); } } return RT_EOK; } void ym310_gps_deinit(void) { ym310_at_exec_cmd("AT+CGPS=0", RT_NULL, 0, YM310_AT_CMD_TIMEOUT_MS); LOG_I("GPS deinitialized"); } /*============================================================================= * Network Check *============================================================================*/ rt_bool_t ym310_check_network_connected(void) { at_resp_type_t resp; char resp_buf[128]; int n, stat; int retry_count = 0; static char once = 1; #define NETWORK_CHECK_RETRIES 2 while (retry_count <= NETWORK_CHECK_RETRIES) { { extern volatile rt_tick_t g_last_mqtt_success_tick; rt_tick_t now = rt_tick_get(); if (now - g_last_mqtt_success_tick < rt_tick_from_millisecond(30000)) { LOG_D("Network check skipped due to recent MQTT success"); return RT_TRUE; } } resp = ym310_at_exec_cmd("AT+CREG?", resp_buf, sizeof(resp_buf), YM310_AT_SHORT_TIMEOUT_MS); if (resp != AT_RESP_OK) { LOG_D("Network check: CREG query failed (resp=%d, retry=%d)", resp, retry_count); retry_count++; if (retry_count <= NETWORK_CHECK_RETRIES) { rt_thread_mdelay(300); continue; } return RT_FALSE; } if (ym310_parse_creg_response(resp_buf, &n, &stat) != 0) { LOG_D("Network check: CREG parse failed"); retry_count++; if (retry_count <= NETWORK_CHECK_RETRIES) { rt_thread_mdelay(500); continue; } return RT_FALSE; } if (stat != 1 && stat != 5) { LOG_D("Network check: CREG stat=%d (not registered)", stat); return RT_FALSE; } resp = ym310_at_exec_cmd("AT+CGATT?", resp_buf, sizeof(resp_buf), YM310_AT_SHORT_TIMEOUT_MS); if (resp != AT_RESP_OK) { LOG_D("Network check: CGATT query failed (resp=%d)", resp); retry_count++; if (retry_count <= NETWORK_CHECK_RETRIES) { rt_thread_mdelay(500); continue; } return RT_FALSE; } int attach_state; if (ym310_parse_cgatt_response(resp_buf, &attach_state) != 0) { LOG_D("Network check: CGATT parse failed"); retry_count++; if (retry_count <= NETWORK_CHECK_RETRIES) { rt_thread_mdelay(500); continue; } return RT_FALSE; } if (attach_state != 1) { LOG_D("Network check: CGATT state=%d (not attached)", attach_state); return RT_FALSE; } resp = ym310_at_exec_cmd("AT+QIPACT?", resp_buf, sizeof(resp_buf), YM310_AT_SHORT_TIMEOUT_MS); if (resp == AT_RESP_OK) { int context_id, pdp_state; char ip_addr[32] = {0}; if (ym310_parse_qipact_response(resp_buf, &context_id, &pdp_state, ip_addr, sizeof(ip_addr)) == 0) { if (once) { LOG_I("Network check: PDP context_id=%d, state=%d, ip=%s", context_id, pdp_state, ip_addr); once = 0; } else { LOG_D("Network check: PDP context_id=%d, state=%d, ip=%s", context_id, pdp_state, ip_addr); } if (pdp_state == 1) { return RT_TRUE; } else { LOG_D("Network check: PDP not activated (state=%d)", pdp_state); return RT_FALSE; } } else { if (rt_strstr(resp_buf, "1,1") != RT_NULL) { LOG_D("Network check: PDP active (raw check)"); return RT_TRUE; } LOG_D("Network check: PDP parse failed, resp=%s", resp_buf); return RT_FALSE; } } else { LOG_D("Network check: QIPACT query failed (resp=%d)", resp); retry_count++; if (retry_count <= NETWORK_CHECK_RETRIES) { rt_thread_mdelay(500); continue; } return RT_FALSE; } break; } return RT_FALSE; } rt_bool_t ym310_check_mqtt_connected(rt_uint8_t client_idx) { if (client_idx >= MQTT_INSTANCE_MAX) { return RT_FALSE; } return (g_mqtt_instances[client_idx].state == MQTT_STATE_CONNECTED); } /*============================================================================= * Module Reinitialization *============================================================================*/ int ym310_module_reinit(void) { at_resp_type_t resp; char resp_buf[128]; int context_id, pdp_state; LOG_W("Attempting module reinitialization..."); resp = ym310_at_exec_cmd("AT+QIPACT?", resp_buf, sizeof(resp_buf), YM310_AT_CMD_TIMEOUT_MS); if (resp == AT_RESP_OK) { if (ym310_parse_qipact_response(resp_buf, &context_id, &pdp_state, RT_NULL, 0) == 0) { if (pdp_state == 1) { LOG_I("PDP already active (context=%d), no reactivation needed", context_id); return RT_EOK; } } else if (rt_strstr(resp_buf, "1,1") != RT_NULL) { LOG_I("PDP already active (raw check), no reactivation needed"); return RT_EOK; } } LOG_I("Reactivating PDP..."); resp = ym310_at_exec_cmd("AT+QIPACT=1", RT_NULL, 0, YM310_AT_LONG_TIMEOUT_MS); if (resp == AT_RESP_OK) { LOG_I("PDP reactivated"); return RT_EOK; } resp = ym310_at_exec_cmd("AT+QIPACT?", resp_buf, sizeof(resp_buf), YM310_AT_CMD_TIMEOUT_MS); if (resp == AT_RESP_OK) { if (rt_strstr(resp_buf, "1,1") != RT_NULL || rt_strstr(resp_buf, "1,1,") != RT_NULL) { LOG_I("PDP is actually active, ignoring reactivation error"); return RT_EOK; } } LOG_W("Full re-registration may be required"); return -RT_ERROR; } /*============================================================================= * Maintenance Thread *============================================================================*/ #include "ym310_protecl.h" extern SYS_INFO sys_info; /** * @brief 判断NTP时间是否已同步成功 * @return 1=已同步, 0=未同步 * * 判断逻辑:当前系统时间若为2026年5月及以前,认为未同步 * 因为设备实际运行时间在2026年6月之后 */ static uint8_t is_ntp_synced(void) { extern struct rtc_time time_now; /* 计算当前时间的 (年*12 + 月) 值 */ uint32_t current_value = (uint32_t)(time_now.year - 2000) * 12 + time_now.month; /* 2026年5月的参考值:26*12 + 5 = 317 */ uint32_t threshold = 26 * 12 + 5; // rt_kprintf("current_value %d threshold %d res = %d" , current_value ,threshold, (current_value > threshold)); /* 如果当前时间 <= 2026年5月,认为NTP未同步 */ if (current_value <= threshold) { return 0; /* 未同步 */ } return 1; /* 已同步 */ } static void ym310_maintain_thread_entry(void *param) { #define MAIN_LOOP_DELAY_MS 100 #define GPS_CYCLE_MS 600000 / 10 // 现在也就是一分钟一次 #define MAINTAIN_CYCLE_MS 120000 #define NTP_CYCLE_MS 600000 * 6 #define GPS_LOOPS (GPS_CYCLE_MS / MAIN_LOOP_DELAY_MS) #define MAINTAIN_LOOPS (MAINTAIN_CYCLE_MS / MAIN_LOOP_DELAY_MS) #define NTP_LOOPS (NTP_CYCLE_MS / MAIN_LOOP_DELAY_MS) static rt_uint32_t loop_counter = 1; static char first_ntp_attempted = 1; rt_thread_mdelay(5000); if (ym310_gps_init() != RT_EOK) { LOG_W("GPS init deferred, will retry later"); } while (1) { if (loop_counter % MAINTAIN_LOOPS == 0) { rt_bool_t network_connected = ym310_check_network_connected(); s_last_network_connected = network_connected; if (!network_connected) { s_net_fail_count++; LOG_D("Network check failed (count=%d/%d)", s_net_fail_count, NET_FAIL_THRESHOLD); if (s_net_fail_count >= NET_FAIL_THRESHOLD) { LOG_W("Network disconnected! Attempting recovery..."); if (ym310_module_reinit() == RT_EOK) { LOG_I("Network recovered"); s_net_fail_count = 0; } for (int i = 0; i < MQTT_INSTANCE_MAX; i++) { if (g_mqtt_instances[i].config.enabled) { if (g_mqtt_instances[i].state == MQTT_STATE_CONNECTED) { g_mqtt_instances[i].state = MQTT_STATE_DISCONNECTED; } } } } } else { if (s_net_fail_count > 0) { LOG_D("Network check passed, resetting failure counter"); s_net_fail_count = 0; } } char csq_buf[64]; int rssi, ber; if (ym310_at_exec_cmd("AT+CSQ", csq_buf, sizeof(csq_buf), YM310_AT_SHORT_TIMEOUT_MS) == AT_RESP_OK) { if (ym310_parse_csq_response(csq_buf, &rssi, &ber) == 0) { LOG_D("Signal quality: RSSI=%d, BER=%d", rssi, ber); sys_info.csq = rssi; } } else { LOG_D("CSQ query failed"); } } if (loop_counter % GPS_LOOPS == 0) { if (ym310_gps_get_info() == RT_EOK) { LOG_D("GPS updated: %s, %s", dev_info.latitude, dev_info.longitude); } } /* 立即 NTP 同步:当 RTC 是默认值时,第一次网络就绪后立即执行一次 */ if (first_ntp_attempted && (is_ntp_synced() == 0)) { if (ym310_check_network_connected()) { LOG_I("RTC is default value, syncing time immediately..."); if (ym310_ntp_sync_time() == RT_EOK) { first_ntp_attempted = 0; LOG_I("Initial NTP sync completed, RTC updated"); } else { LOG_W("Initial NTP sync failed, will retry on next network check"); } } // else // { // LOG_W("s_last_network_connected == 0"); // } } // else // { // LOG_I("year = %d mon = %d day = %d" , time_now.year , time_now.month , time_now.date ); // } if (loop_counter % NTP_LOOPS == 0) { if (s_last_network_connected) { ym310_ntp_sync_time(); } } loop_counter++; if (loop_counter == 0) loop_counter = 1; rt_thread_mdelay(MAIN_LOOP_DELAY_MS); } ym310_gps_deinit(); LOG_I("Maintenance thread stopped"); } /*============================================================================= * Public API *============================================================================*/ rt_thread_t ym310_start_maintain_thread(void) { g_maintain_thread = rt_thread_create("ym310_maint", ym310_maintain_thread_entry, RT_NULL, MAINTAIN_THREAD_STACK_SIZE, MAINTAIN_THREAD_PRIORITY, 10); if (g_maintain_thread == RT_NULL) { LOG_E("Failed to create maintenance thread!"); return RT_NULL; } rt_thread_startup(g_maintain_thread); return g_maintain_thread; } void ym310_maintain_thread_stop(void) { s_maintain_thread_running = RT_FALSE; } /*============================================================================= * GCJ-02 Transform Test Command (MSH) - Integer Output Only *============================================================================*/ static void transform_test(int argc, char **argv) { const char *lat_nmea, *ns, *lon_nmea, *ew; if (argc >= 5) { lon_nmea = argv[1]; ew = argv[2]; lat_nmea = argv[3]; ns = argv[4]; } else { lon_nmea = "11339.66602"; ew = "E"; lat_nmea = "3448.60878"; ns = "N"; rt_kprintf("[TEST] Using default coords: %s%s, %s%s\n", lon_nmea, ew, lat_nmea, ns); } /* Parse WGS84 for display */ double wgs84_lat = 0.0, wgs84_lon = 0.0; char temp[16]; strncpy(temp, lat_nmea, sizeof(temp) - 1); temp[sizeof(temp) - 1] = '\0'; char *dot = strchr(temp, '.'); if (dot) { char deg_str[3] = {0}; strncpy(deg_str, temp, 2); int deg = atoi(deg_str); double minutes = atof(temp + 2); wgs84_lat = (double)deg + minutes / 60.0; if (*ns == 'S' || *ns == 's') wgs84_lat = -wgs84_lat; } strncpy(temp, lon_nmea, sizeof(temp) - 1); temp[sizeof(temp) - 1] = '\0'; dot = strchr(temp, '.'); if (dot) { char deg_str[4] = {0}; strncpy(deg_str, temp, 3); int deg = atoi(deg_str); double minutes = atof(temp + 3); wgs84_lon = (double)deg + minutes / 60.0; if (*ew == 'W' || *ew == 'w') wgs84_lon = -wgs84_lon; } long wgs84_lat_int = (long)(wgs84_lat * 1e7 + (wgs84_lat >= 0 ? 0.5 : -0.5)); long wgs84_lon_int = (long)(wgs84_lon * 1e7 + (wgs84_lon >= 0 ? 0.5 : -0.5)); /* Convert to GCJ-02 */ int gcj_lat_1e7, gcj_lon_1e7; nmea_pair_to_gcj02_int(lat_nmea, *ns, lon_nmea, *ew, &gcj_lat_1e7, &gcj_lon_1e7); /* Print results */ rt_kprintf("\n========================================\n"); rt_kprintf("[WGS-84] %ld.%07ld, %ld.%07ld\n", wgs84_lat_int / 10000000, (wgs84_lat_int % 10000000) >= 0 ? (wgs84_lat_int % 10000000) : -(wgs84_lat_int % 10000000), wgs84_lon_int / 10000000, (wgs84_lon_int % 10000000) >= 0 ? (wgs84_lon_int % 10000000) : -(wgs84_lon_int % 10000000)); rt_kprintf("[GCJ-02] %d.%07d, %d.%07d\n", gcj_lat_1e7 / 10000000, (gcj_lat_1e7 % 10000000) >= 0 ? (gcj_lat_1e7 % 10000000) : -(gcj_lat_1e7 % 10000000), gcj_lon_1e7 / 10000000, (gcj_lon_1e7 % 10000000) >= 0 ? (gcj_lon_1e7 % 10000000) : -(gcj_lon_1e7 % 10000000)); long dlat = (long)gcj_lat_1e7 - wgs84_lat_int; long dlon = (long)gcj_lon_1e7 - wgs84_lon_int; rt_kprintf("[OFFSET] dLat = %ld (1e-7 deg), dLon = %ld (1e-7 deg)\n", dlat, dlon); rt_kprintf("========================================\n\n"); } MSH_CMD_EXPORT(transform_test, Test WGS84 to GCJ - 02 conversion)