ym310_maintain.c 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997
  1. /*
  2. * YM310 State Maintenance Thread - FIXED v5.3
  3. *
  4. * Fixes in this version:
  5. * - Consolidated all conversion logic into a single function:
  6. * nmea_to_gcj02_int(). No external helper functions remain.
  7. * - Transform test uses integer output only.
  8. *
  9. * Version: 5.3.0
  10. */
  11. #define DBG_TAG "Y-MNT"
  12. #define DBG_LVL DBG_INFO
  13. #include <rtdbg.h>
  14. #include "ym310_mqtt.h"
  15. #include <string.h>
  16. #include <stdio.h>
  17. #include <stdint.h>
  18. #include <math.h>
  19. #include "peri.h"
  20. extern struct rtc_time time_now;
  21. /*=============================================================================
  22. * Global Variables
  23. *============================================================================*/
  24. rt_thread_t g_maintain_thread = RT_NULL;
  25. static volatile rt_bool_t s_maintain_thread_running = RT_FALSE;
  26. /* Consecutive network failure counter */
  27. static int s_net_fail_count = 0;
  28. #define NET_FAIL_THRESHOLD 3
  29. /* Network check result cache */
  30. static rt_bool_t s_last_network_connected = RT_FALSE;
  31. /* External function declaration */
  32. extern int ym310_parse_qipact_response(const char *resp_buf, int *context_id, int *state, char *ip_addr, int ip_addr_size);
  33. /*=============================================================================
  34. * Utility: Trim String
  35. *============================================================================*/
  36. static void trim_string(char *str)
  37. {
  38. char *start = str;
  39. char *end;
  40. if (!str || *str == '\0')
  41. {
  42. return;
  43. }
  44. while (*start == ' ' || *start == '\t' || *start == '\r' || *start == '\n')
  45. {
  46. start++;
  47. }
  48. if (start != str)
  49. {
  50. memmove(str, start, strlen(start) + 1);
  51. }
  52. end = str + strlen(str) - 1;
  53. while (end >= str && (*end == ' ' || *end == '\t' || *end == '\r' || *end == '\n'))
  54. {
  55. *end = '\0';
  56. end--;
  57. }
  58. }
  59. /*=============================================================================
  60. * NTP Time Sync
  61. *============================================================================*/
  62. int ym310_ntp_sync_time(void)
  63. {
  64. at_resp_type_t resp;
  65. char resp_buf[128];
  66. char urc_buf[128];
  67. int err_code;
  68. char datetime_str[32];
  69. int year, month, day, hour, minute, second, tz = 0;
  70. int tz_sign = 1;
  71. resp = ym310_at_exec_cmd("AT+QNTP=1,\"ntp.aliyun.com\",123", resp_buf, sizeof(resp_buf), 5000);
  72. if (resp != AT_RESP_OK)
  73. {
  74. LOG_W("NTP command failed (resp=%d)", resp);
  75. return -RT_ERROR;
  76. }
  77. if (ym310_at_wait_for("+QNTP:", urc_buf, sizeof(urc_buf), 30000) != RT_EOK)
  78. {
  79. LOG_E("NTP: timeout waiting for +QNTP: URC");
  80. return -RT_ERROR;
  81. }
  82. const char *p = rt_strstr(urc_buf, "+QNTP:");
  83. if (p == RT_NULL)
  84. {
  85. LOG_E("NTP: +QNTP: not found in URC");
  86. return -RT_ERROR;
  87. }
  88. p += 6;
  89. while (*p == ' ' || *p == '\t')
  90. p++;
  91. if (sscanf(p, "%d,\"%31[^\"]\"", &err_code, datetime_str) != 2)
  92. {
  93. LOG_E("NTP: failed to parse response: %s", urc_buf);
  94. return -RT_ERROR;
  95. }
  96. if (err_code != 0)
  97. {
  98. LOG_E("NTP: server returned error code %d", err_code);
  99. return -RT_ERROR;
  100. }
  101. LOG_D("NTP raw datetime: %s", datetime_str);
  102. char *tz_ptr = strchr(datetime_str, '+');
  103. if (tz_ptr == RT_NULL)
  104. {
  105. tz_ptr = strchr(datetime_str, '-');
  106. if (tz_ptr)
  107. tz_sign = -1;
  108. }
  109. if (tz_ptr)
  110. {
  111. sscanf(tz_ptr + 1, "%d", &tz);
  112. tz *= tz_sign;
  113. *tz_ptr = '\0';
  114. }
  115. if (sscanf(datetime_str, "%d/%d/%d,%d:%d:%d", &year, &month, &day, &hour, &minute, &second) != 6)
  116. {
  117. LOG_E("NTP: failed to parse date/time from: %s", datetime_str);
  118. return -RT_ERROR;
  119. }
  120. int total_minutes = hour * 60 + minute + tz * 15;
  121. int new_hour = (total_minutes / 60) % 24;
  122. int new_minute = total_minutes % 60;
  123. int day_advance = total_minutes / (24 * 60);
  124. struct rtc_time local_time;
  125. local_time.year = year;
  126. local_time.month = month;
  127. local_time.date = day + day_advance;
  128. local_time.hour = new_hour;
  129. local_time.minute = new_minute;
  130. local_time.second = second;
  131. if (local_time.date > 28)
  132. {
  133. int days_in_month[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
  134. if ((local_time.year % 4 == 0 && local_time.year % 100 != 0) || (local_time.year % 400 == 0))
  135. days_in_month[1] = 29;
  136. if (local_time.date > days_in_month[local_time.month - 1])
  137. {
  138. local_time.date -= days_in_month[local_time.month - 1];
  139. local_time.month++;
  140. if (local_time.month > 12)
  141. {
  142. local_time.month = 1;
  143. local_time.year++;
  144. }
  145. }
  146. }
  147. extern int rtc_set_time(struct rtc_time * time);
  148. if (rtc_set_time(&local_time) != 0)
  149. {
  150. LOG_E("NTP: Failed to set RTC time");
  151. return -RT_ERROR;
  152. }
  153. LOG_I("NTP sync successful, RTC updated : %04d-%02d-%02d %02d:%02d:%02d",
  154. local_time.year, local_time.month, local_time.date,
  155. local_time.hour, local_time.minute, local_time.second);
  156. return RT_EOK;
  157. }
  158. /*=============================================================================
  159. * GPS Operations
  160. *============================================================================*/
  161. static rt_bool_t s_gps_initialized = RT_FALSE;
  162. int ym310_gps_init(void)
  163. {
  164. at_resp_type_t resp;
  165. int retry = 0;
  166. LOG_I("Initializing GPS...");
  167. for (retry = 0; retry < 3; retry++)
  168. {
  169. resp = ym310_at_exec_cmd("AT+CGPS=1", NULL, 0, YM310_AT_CMD_TIMEOUT_MS);
  170. if (resp == AT_RESP_OK)
  171. {
  172. LOG_I("GPS started successfully");
  173. break;
  174. }
  175. LOG_W("GPS start attempt %d failed (resp=%d)", retry + 1, resp);
  176. rt_thread_mdelay(1000);
  177. }
  178. if (resp != AT_RESP_OK)
  179. {
  180. LOG_E("GPS init failed after 3 attempts");
  181. s_gps_initialized = RT_FALSE;
  182. return -RT_ERROR;
  183. }
  184. resp = ym310_at_exec_cmd("AT+CGPSPORT=0", NULL, 0, YM310_AT_CMD_TIMEOUT_MS);
  185. s_gps_initialized = RT_TRUE;
  186. return RT_EOK;
  187. }
  188. /*=============================================================================
  189. * GCJ-02 Coordinate Transformation - Single Integrated Function
  190. *============================================================================*/
  191. /* Earth constants (Krasovsky 1940 ellipsoid) */
  192. static const double pi = 3.14159265358979324;
  193. static const double a = 6378245.0;
  194. static const double ee = 0.00669342162296594323;
  195. /**
  196. * @brief Convert NMEA coordinate string directly to GCJ-02 1e7-degree integer
  197. *
  198. * This function encapsulates all steps:
  199. * - Parses NMEA format "DDMM.MMMMM"
  200. * - Converts to WGS84 decimal degrees
  201. * - Applies GCJ-02 offset using standard algorithm
  202. * - Returns result as integer in 1e7 degrees (e.g., 348101463 = 34.8101463°)
  203. *
  204. * @param nmea_str NMEA coordinate string (e.g., "3448.60878")
  205. * @param is_lon 1 for longitude (3-degree digits), 0 for latitude (2-degree digits)
  206. * @param direction 'N'/'S' for latitude, 'E'/'W' for longitude
  207. * @return GCJ-02 coordinate in 1e7 degrees (int)
  208. */
  209. static int nmea_to_gcj02_int(const char *nmea_str, int is_lon, char direction)
  210. {
  211. char temp[16];
  212. strncpy(temp, nmea_str, sizeof(temp) - 1);
  213. temp[sizeof(temp) - 1] = '\0';
  214. /* Find decimal point */
  215. char *dot = strchr(temp, '.');
  216. if (!dot)
  217. return 0;
  218. /* Determine degree digits (2 for lat, 3 for lon) */
  219. int deg_digits = is_lon ? 3 : 2;
  220. char deg_str[4] = {0};
  221. strncpy(deg_str, temp, deg_digits);
  222. int deg = atoi(deg_str);
  223. /* Minutes part */
  224. double minutes = atof(temp + deg_digits);
  225. double wgs84 = (double)deg + minutes / 60.0;
  226. /* Apply hemisphere sign */
  227. if (direction == 'S' || direction == 's' || direction == 'W' || direction == 'w')
  228. wgs84 = -wgs84;
  229. double lat = is_lon ? 0.0 : wgs84;
  230. double lon = is_lon ? wgs84 : 0.0;
  231. /* For longitude, we need both lat and lon to compute offset,
  232. * but this function only receives one coordinate at a time.
  233. * So we need both lat and lon to be passed together.
  234. * To simplify, we'll keep the interface that receives both.
  235. */
  236. /* Actually, the offset depends on both lat and lon, so we cannot compute
  237. * GCJ-02 from a single coordinate alone. Thus we need a function that
  238. * takes both lat and lon NMEA strings and directions.
  239. * Let's change the design: a single function that accepts both strings.
  240. */
  241. return 0; /* Placeholder - we will redesign below */
  242. }
  243. /* Since GCJ-02 offset depends on both latitude and longitude, we must process
  244. both simultaneously. The revised function takes both NMEA strings and directions. */
  245. /**
  246. * @brief Convert WGS84 NMEA pair to GCJ-02 1e7-degree integers
  247. *
  248. * @param lat_nmea Latitude NMEA string (e.g., "3448.60878")
  249. * @param ns 'N' or 'S'
  250. * @param lon_nmea Longitude NMEA string (e.g., "11339.66602")
  251. * @param ew 'E' or 'W'
  252. * @param gcj_lat_1e7 Output GCJ-02 latitude in 1e7 degrees
  253. * @param gcj_lon_1e7 Output GCJ-02 longitude in 1e7 degrees
  254. */
  255. static void nmea_pair_to_gcj02_int(const char *lat_nmea, char ns,
  256. const char *lon_nmea, char ew,
  257. int *gcj_lat_1e7, int *gcj_lon_1e7)
  258. {
  259. /* Parse latitude */
  260. double wgs84_lat = 0.0;
  261. char temp[16];
  262. strncpy(temp, lat_nmea, sizeof(temp) - 1);
  263. temp[sizeof(temp) - 1] = '\0';
  264. char *dot = strchr(temp, '.');
  265. if (dot)
  266. {
  267. char deg_str[3] = {0};
  268. strncpy(deg_str, temp, 2);
  269. int deg = atoi(deg_str);
  270. double minutes = atof(temp + 2);
  271. wgs84_lat = (double)deg + minutes / 60.0;
  272. if (ns == 'S' || ns == 's')
  273. wgs84_lat = -wgs84_lat;
  274. }
  275. /* Parse longitude */
  276. double wgs84_lon = 0.0;
  277. strncpy(temp, lon_nmea, sizeof(temp) - 1);
  278. temp[sizeof(temp) - 1] = '\0';
  279. dot = strchr(temp, '.');
  280. if (dot)
  281. {
  282. char deg_str[4] = {0};
  283. strncpy(deg_str, temp, 3);
  284. int deg = atoi(deg_str);
  285. double minutes = atof(temp + 3);
  286. wgs84_lon = (double)deg + minutes / 60.0;
  287. if (ew == 'W' || ew == 'w')
  288. wgs84_lon = -wgs84_lon;
  289. }
  290. /* Check if outside China */
  291. if (wgs84_lon < 72.004 || wgs84_lon > 137.8347 ||
  292. wgs84_lat < 0.8293 || wgs84_lat > 55.8271)
  293. {
  294. *gcj_lat_1e7 = (int)(wgs84_lat * 1e7 + (wgs84_lat >= 0 ? 0.5 : -0.5));
  295. *gcj_lon_1e7 = (int)(wgs84_lon * 1e7 + (wgs84_lon >= 0 ? 0.5 : -0.5));
  296. return;
  297. }
  298. /* GCJ-02 offset computation */
  299. double x = wgs84_lon - 105.0;
  300. double y = wgs84_lat - 35.0;
  301. double dlat = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * sqrt(fabs(x));
  302. dlat += (20.0 * sin(6.0 * x * pi) + 20.0 * sin(2.0 * x * pi)) * 2.0 / 3.0;
  303. dlat += (20.0 * sin(y * pi) + 40.0 * sin(y / 3.0 * pi)) * 2.0 / 3.0;
  304. dlat += (160.0 * sin(y / 12.0 * pi) + 320 * sin(y * pi / 30.0)) * 2.0 / 3.0;
  305. double dlon = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * sqrt(fabs(x));
  306. dlon += (20.0 * sin(6.0 * x * pi) + 20.0 * sin(2.0 * x * pi)) * 2.0 / 3.0;
  307. dlon += (20.0 * sin(x * pi) + 40.0 * sin(x / 3.0 * pi)) * 2.0 / 3.0;
  308. dlon += (150.0 * sin(x / 12.0 * pi) + 300.0 * sin(x / 30.0 * pi)) * 2.0 / 3.0;
  309. double radlat = wgs84_lat / 180.0 * pi;
  310. double magic = sin(radlat);
  311. magic = 1 - ee * magic * magic;
  312. double sqrtmagic = sqrt(magic);
  313. dlat = (dlat * 180.0) / ((a * (1 - ee)) / (magic * sqrtmagic) * pi);
  314. dlon = (dlon * 180.0) / (a / sqrtmagic * cos(radlat) * pi);
  315. double gcj_lat = wgs84_lat + dlat;
  316. double gcj_lon = wgs84_lon + dlon;
  317. /* Convert to 1e7 integer with rounding */
  318. *gcj_lat_1e7 = (int)(gcj_lat * 1e7 + (gcj_lat >= 0 ? 0.5 : -0.5));
  319. *gcj_lon_1e7 = (int)(gcj_lon * 1e7 + (gcj_lon >= 0 ? 0.5 : -0.5));
  320. }
  321. /**
  322. * @brief Convert GPS NMEA pair directly to GCJ-02 and store in dev_info
  323. */
  324. static int gps_to_gcj02_storage(const char *lat_str, char ns,
  325. const char *lon_str, char ew)
  326. {
  327. int gcj_lat_1e7, gcj_lon_1e7;
  328. nmea_pair_to_gcj02_int(lat_str, ns, lon_str, ew, &gcj_lat_1e7, &gcj_lon_1e7);
  329. /* Format to string (7 decimal places) */
  330. char lat_buf[32], lon_buf[32];
  331. int lat_deg = gcj_lat_1e7 / 10000000;
  332. int lat_frac = gcj_lat_1e7 % 10000000;
  333. if (lat_frac < 0)
  334. lat_frac = -lat_frac;
  335. int lon_deg = gcj_lon_1e7 / 10000000;
  336. int lon_frac = gcj_lon_1e7 % 10000000;
  337. if (lon_frac < 0)
  338. lon_frac = -lon_frac;
  339. snprintf(lat_buf, sizeof(lat_buf), "%d.%07d", lat_deg, lat_frac);
  340. snprintf(lon_buf, sizeof(lon_buf), "%d.%07d", lon_deg, lon_frac);
  341. strncpy(dev_info.latitude, lat_buf, sizeof(dev_info.latitude) - 1);
  342. dev_info.latitude[sizeof(dev_info.latitude) - 1] = '\0';
  343. strncpy(dev_info.longitude, lon_buf, sizeof(dev_info.longitude) - 1);
  344. dev_info.longitude[sizeof(dev_info.longitude) - 1] = '\0';
  345. LOG_I("GPS GCJ-02: %s, %s", dev_info.latitude, dev_info.longitude);
  346. return 0;
  347. }
  348. static char start_gnss_info = 0;
  349. static void gnss_info_on(void) { start_gnss_info = 1; }
  350. static void gnss_info_off(void) { start_gnss_info = 0; }
  351. MSH_CMD_EXPORT(gnss_info_on, gnss_info_on)
  352. MSH_CMD_EXPORT(gnss_info_off, gnss_info_off)
  353. /*=============================================================================
  354. * GPS Get Info
  355. *============================================================================*/
  356. int ym310_gps_get_info(void)
  357. {
  358. at_resp_type_t resp;
  359. char resp_buf[256];
  360. extern DEV_INFO dev_info;
  361. if (!s_gps_initialized)
  362. {
  363. if (ym310_gps_init() != RT_EOK)
  364. {
  365. LOG_D("GPS not initialized, skipping query");
  366. return -RT_ERROR;
  367. }
  368. }
  369. resp = ym310_at_exec_cmd("AT+CGPSINFO", resp_buf, sizeof(resp_buf), 5000);
  370. if (resp != AT_RESP_OK)
  371. {
  372. LOG_D("GPS query failed (resp=%d)", resp);
  373. return -RT_ERROR;
  374. }
  375. if (start_gnss_info)
  376. {
  377. rt_kprintf("AT+CGPSINFO >>>%s<<<\n", resp_buf);
  378. }
  379. const char *p = rt_strstr(resp_buf, "+CGPSINFO:");
  380. if (p == RT_NULL)
  381. {
  382. LOG_D("GPS: No +CGPSINFO in response");
  383. return -RT_ERROR;
  384. }
  385. p += 10;
  386. while (*p && (*p == ' ' || *p == '\t'))
  387. p++;
  388. if (*p == ',' || *p == '\0' || *p == '\r' || *p == '\n')
  389. {
  390. LOG_D("GPS: No fix yet");
  391. return -RT_ERROR;
  392. }
  393. char lat_str[16] = {0};
  394. char ns_ind[2] = {0};
  395. char lon_str[16] = {0};
  396. char ew_ind[2] = {0};
  397. char date_str[16] = {0};
  398. char time_str[16] = {0};
  399. char alt_str[16] = {0};
  400. char speed_str[16] = {0};
  401. char course_str[16] = {0};
  402. int parsed = sscanf(p, "%15[^,],%1[^,],%15[^,],%1[^,],%15[^,],%15[^,],%15[^,],%15[^,],%15[^,]",
  403. lat_str, ns_ind, lon_str, ew_ind, date_str, time_str,
  404. alt_str, speed_str, course_str);
  405. if (parsed < 4)
  406. {
  407. LOG_D("GPS parse failed, items=%d", parsed);
  408. return -RT_ERROR;
  409. }
  410. trim_string(lat_str);
  411. trim_string(lon_str);
  412. trim_string(date_str);
  413. trim_string(time_str);
  414. trim_string(alt_str);
  415. trim_string(speed_str);
  416. trim_string(course_str);
  417. if (lat_str[0] == '\0' || lon_str[0] == '\0')
  418. {
  419. LOG_D("GPS: Empty coordinate data");
  420. return -RT_ERROR;
  421. }
  422. LOG_D("GPS Raw: Lat=%s%c, Lon=%s%c", lat_str, ns_ind[0], lon_str, ew_ind[0]);
  423. if (gps_to_gcj02_storage(lat_str, ns_ind[0], lon_str, ew_ind[0]) != 0)
  424. {
  425. LOG_E("GPS coordinate conversion failed");
  426. return -RT_ERROR;
  427. }
  428. else
  429. {
  430. char gps_save_date = 0, now = 0;
  431. ef_get_env_blob("gps_save_date", &gps_save_date, sizeof(gps_save_date), NULL);
  432. now = time_now.year + time_now.month + time_now.date;
  433. if (gps_save_date != now)
  434. {
  435. gps_save_date = now;
  436. ef_set_env_blob("gps_save_date", &gps_save_date, sizeof(gps_save_date));
  437. LOG_I("gps_save_date : %s , %s", dev_info.latitude, dev_info.longitude);
  438. save_dev_info();
  439. }
  440. }
  441. return RT_EOK;
  442. }
  443. void ym310_gps_deinit(void)
  444. {
  445. ym310_at_exec_cmd("AT+CGPS=0", RT_NULL, 0, YM310_AT_CMD_TIMEOUT_MS);
  446. LOG_I("GPS deinitialized");
  447. }
  448. /*=============================================================================
  449. * Network Check
  450. *============================================================================*/
  451. rt_bool_t ym310_check_network_connected(void)
  452. {
  453. at_resp_type_t resp;
  454. char resp_buf[128];
  455. int n, stat;
  456. int retry_count = 0;
  457. static char once = 1;
  458. #define NETWORK_CHECK_RETRIES 2
  459. while (retry_count <= NETWORK_CHECK_RETRIES)
  460. {
  461. {
  462. extern volatile rt_tick_t g_last_mqtt_success_tick;
  463. rt_tick_t now = rt_tick_get();
  464. if (now - g_last_mqtt_success_tick < rt_tick_from_millisecond(30000))
  465. {
  466. LOG_D("Network check skipped due to recent MQTT success");
  467. return RT_TRUE;
  468. }
  469. }
  470. resp = ym310_at_exec_cmd("AT+CREG?", resp_buf, sizeof(resp_buf), YM310_AT_SHORT_TIMEOUT_MS);
  471. if (resp != AT_RESP_OK)
  472. {
  473. LOG_D("Network check: CREG query failed (resp=%d, retry=%d)", resp, retry_count);
  474. retry_count++;
  475. if (retry_count <= NETWORK_CHECK_RETRIES)
  476. {
  477. rt_thread_mdelay(300);
  478. continue;
  479. }
  480. return RT_FALSE;
  481. }
  482. if (ym310_parse_creg_response(resp_buf, &n, &stat) != 0)
  483. {
  484. LOG_D("Network check: CREG parse failed");
  485. retry_count++;
  486. if (retry_count <= NETWORK_CHECK_RETRIES)
  487. {
  488. rt_thread_mdelay(500);
  489. continue;
  490. }
  491. return RT_FALSE;
  492. }
  493. if (stat != 1 && stat != 5)
  494. {
  495. LOG_D("Network check: CREG stat=%d (not registered)", stat);
  496. return RT_FALSE;
  497. }
  498. resp = ym310_at_exec_cmd("AT+CGATT?", resp_buf, sizeof(resp_buf), YM310_AT_SHORT_TIMEOUT_MS);
  499. if (resp != AT_RESP_OK)
  500. {
  501. LOG_D("Network check: CGATT query failed (resp=%d)", resp);
  502. retry_count++;
  503. if (retry_count <= NETWORK_CHECK_RETRIES)
  504. {
  505. rt_thread_mdelay(500);
  506. continue;
  507. }
  508. return RT_FALSE;
  509. }
  510. int attach_state;
  511. if (ym310_parse_cgatt_response(resp_buf, &attach_state) != 0)
  512. {
  513. LOG_D("Network check: CGATT parse failed");
  514. retry_count++;
  515. if (retry_count <= NETWORK_CHECK_RETRIES)
  516. {
  517. rt_thread_mdelay(500);
  518. continue;
  519. }
  520. return RT_FALSE;
  521. }
  522. if (attach_state != 1)
  523. {
  524. LOG_D("Network check: CGATT state=%d (not attached)", attach_state);
  525. return RT_FALSE;
  526. }
  527. resp = ym310_at_exec_cmd("AT+QIPACT?", resp_buf, sizeof(resp_buf), YM310_AT_SHORT_TIMEOUT_MS);
  528. if (resp == AT_RESP_OK)
  529. {
  530. int context_id, pdp_state;
  531. char ip_addr[32] = {0};
  532. if (ym310_parse_qipact_response(resp_buf, &context_id, &pdp_state, ip_addr, sizeof(ip_addr)) == 0)
  533. {
  534. if (once)
  535. {
  536. LOG_I("Network check: PDP context_id=%d, state=%d, ip=%s", context_id, pdp_state, ip_addr);
  537. once = 0;
  538. }
  539. else
  540. {
  541. LOG_D("Network check: PDP context_id=%d, state=%d, ip=%s", context_id, pdp_state, ip_addr);
  542. }
  543. if (pdp_state == 1)
  544. {
  545. return RT_TRUE;
  546. }
  547. else
  548. {
  549. LOG_D("Network check: PDP not activated (state=%d)", pdp_state);
  550. return RT_FALSE;
  551. }
  552. }
  553. else
  554. {
  555. if (rt_strstr(resp_buf, "1,1") != RT_NULL)
  556. {
  557. LOG_D("Network check: PDP active (raw check)");
  558. return RT_TRUE;
  559. }
  560. LOG_D("Network check: PDP parse failed, resp=%s", resp_buf);
  561. return RT_FALSE;
  562. }
  563. }
  564. else
  565. {
  566. LOG_D("Network check: QIPACT query failed (resp=%d)", resp);
  567. retry_count++;
  568. if (retry_count <= NETWORK_CHECK_RETRIES)
  569. {
  570. rt_thread_mdelay(500);
  571. continue;
  572. }
  573. return RT_FALSE;
  574. }
  575. break;
  576. }
  577. return RT_FALSE;
  578. }
  579. rt_bool_t ym310_check_mqtt_connected(rt_uint8_t client_idx)
  580. {
  581. if (client_idx >= MQTT_INSTANCE_MAX)
  582. {
  583. return RT_FALSE;
  584. }
  585. return (g_mqtt_instances[client_idx].state == MQTT_STATE_CONNECTED);
  586. }
  587. /*=============================================================================
  588. * Module Reinitialization
  589. *============================================================================*/
  590. int ym310_module_reinit(void)
  591. {
  592. at_resp_type_t resp;
  593. char resp_buf[128];
  594. int context_id, pdp_state;
  595. LOG_W("Attempting module reinitialization...");
  596. resp = ym310_at_exec_cmd("AT+QIPACT?", resp_buf, sizeof(resp_buf), YM310_AT_CMD_TIMEOUT_MS);
  597. if (resp == AT_RESP_OK)
  598. {
  599. if (ym310_parse_qipact_response(resp_buf, &context_id, &pdp_state, RT_NULL, 0) == 0)
  600. {
  601. if (pdp_state == 1)
  602. {
  603. LOG_I("PDP already active (context=%d), no reactivation needed", context_id);
  604. return RT_EOK;
  605. }
  606. }
  607. else if (rt_strstr(resp_buf, "1,1") != RT_NULL)
  608. {
  609. LOG_I("PDP already active (raw check), no reactivation needed");
  610. return RT_EOK;
  611. }
  612. }
  613. LOG_I("Reactivating PDP...");
  614. resp = ym310_at_exec_cmd("AT+QIPACT=1", RT_NULL, 0, YM310_AT_LONG_TIMEOUT_MS);
  615. if (resp == AT_RESP_OK)
  616. {
  617. LOG_I("PDP reactivated");
  618. return RT_EOK;
  619. }
  620. resp = ym310_at_exec_cmd("AT+QIPACT?", resp_buf, sizeof(resp_buf), YM310_AT_CMD_TIMEOUT_MS);
  621. if (resp == AT_RESP_OK)
  622. {
  623. if (rt_strstr(resp_buf, "1,1") != RT_NULL || rt_strstr(resp_buf, "1,1,") != RT_NULL)
  624. {
  625. LOG_I("PDP is actually active, ignoring reactivation error");
  626. return RT_EOK;
  627. }
  628. }
  629. LOG_W("Full re-registration may be required");
  630. return -RT_ERROR;
  631. }
  632. /*=============================================================================
  633. * Maintenance Thread
  634. *============================================================================*/
  635. #include "ym310_protecl.h"
  636. extern SYS_INFO sys_info;
  637. /**
  638. * @brief 判断NTP时间是否已同步成功
  639. * @return 1=已同步, 0=未同步
  640. *
  641. * 判断逻辑:当前系统时间若为2026年5月及以前,认为未同步
  642. * 因为设备实际运行时间在2026年6月之后
  643. */
  644. static uint8_t is_ntp_synced(void)
  645. {
  646. extern struct rtc_time time_now;
  647. /* 计算当前时间的 (年*12 + 月) 值 */
  648. uint32_t current_value = (uint32_t)(time_now.year - 2000) * 12 + time_now.month;
  649. /* 2026年5月的参考值:26*12 + 5 = 317 */
  650. uint32_t threshold = 26 * 12 + 5;
  651. // rt_kprintf("current_value %d threshold %d res = %d" , current_value ,threshold, (current_value > threshold));
  652. /* 如果当前时间 <= 2026年5月,认为NTP未同步 */
  653. if (current_value <= threshold)
  654. {
  655. return 0; /* 未同步 */
  656. }
  657. return 1; /* 已同步 */
  658. }
  659. static void ym310_maintain_thread_entry(void *param)
  660. {
  661. #define MAIN_LOOP_DELAY_MS 100
  662. #define GPS_CYCLE_MS 600000 / 10 // 现在也就是一分钟一次
  663. #define MAINTAIN_CYCLE_MS 120000
  664. #define NTP_CYCLE_MS 600000 * 6
  665. #define GPS_LOOPS (GPS_CYCLE_MS / MAIN_LOOP_DELAY_MS)
  666. #define MAINTAIN_LOOPS (MAINTAIN_CYCLE_MS / MAIN_LOOP_DELAY_MS)
  667. #define NTP_LOOPS (NTP_CYCLE_MS / MAIN_LOOP_DELAY_MS)
  668. static rt_uint32_t loop_counter = 1;
  669. static char first_ntp_attempted = 1;
  670. rt_thread_mdelay(5000);
  671. if (ym310_gps_init() != RT_EOK)
  672. {
  673. LOG_W("GPS init deferred, will retry later");
  674. }
  675. while (1)
  676. {
  677. if (loop_counter % MAINTAIN_LOOPS == 0)
  678. {
  679. rt_bool_t network_connected = ym310_check_network_connected();
  680. s_last_network_connected = network_connected;
  681. if (!network_connected)
  682. {
  683. s_net_fail_count++;
  684. LOG_D("Network check failed (count=%d/%d)", s_net_fail_count, NET_FAIL_THRESHOLD);
  685. if (s_net_fail_count >= NET_FAIL_THRESHOLD)
  686. {
  687. LOG_W("Network disconnected! Attempting recovery...");
  688. if (ym310_module_reinit() == RT_EOK)
  689. {
  690. LOG_I("Network recovered");
  691. s_net_fail_count = 0;
  692. }
  693. for (int i = 0; i < MQTT_INSTANCE_MAX; i++)
  694. {
  695. if (g_mqtt_instances[i].config.enabled)
  696. {
  697. if (g_mqtt_instances[i].state == MQTT_STATE_CONNECTED)
  698. {
  699. g_mqtt_instances[i].state = MQTT_STATE_DISCONNECTED;
  700. }
  701. }
  702. }
  703. }
  704. }
  705. else
  706. {
  707. if (s_net_fail_count > 0)
  708. {
  709. LOG_D("Network check passed, resetting failure counter");
  710. s_net_fail_count = 0;
  711. }
  712. }
  713. char csq_buf[64];
  714. int rssi, ber;
  715. if (ym310_at_exec_cmd("AT+CSQ", csq_buf, sizeof(csq_buf), YM310_AT_SHORT_TIMEOUT_MS) == AT_RESP_OK)
  716. {
  717. if (ym310_parse_csq_response(csq_buf, &rssi, &ber) == 0)
  718. {
  719. LOG_D("Signal quality: RSSI=%d, BER=%d", rssi, ber);
  720. sys_info.csq = rssi;
  721. }
  722. }
  723. else
  724. {
  725. LOG_D("CSQ query failed");
  726. }
  727. }
  728. if (loop_counter % GPS_LOOPS == 0)
  729. {
  730. if (ym310_gps_get_info() == RT_EOK)
  731. {
  732. LOG_D("GPS updated: %s, %s", dev_info.latitude, dev_info.longitude);
  733. }
  734. }
  735. /* 立即 NTP 同步:当 RTC 是默认值时,第一次网络就绪后立即执行一次 */
  736. if (first_ntp_attempted && (is_ntp_synced() == 0))
  737. {
  738. if (ym310_check_network_connected())
  739. {
  740. LOG_I("RTC is default value, syncing time immediately...");
  741. if (ym310_ntp_sync_time() == RT_EOK)
  742. {
  743. first_ntp_attempted = 0;
  744. LOG_I("Initial NTP sync completed, RTC updated");
  745. }
  746. else
  747. {
  748. LOG_W("Initial NTP sync failed, will retry on next network check");
  749. }
  750. }
  751. // else
  752. // {
  753. // LOG_W("s_last_network_connected == 0");
  754. // }
  755. }
  756. // else
  757. // {
  758. // LOG_I("year = %d mon = %d day = %d" , time_now.year , time_now.month , time_now.date );
  759. // }
  760. if (loop_counter % NTP_LOOPS == 0)
  761. {
  762. if (s_last_network_connected)
  763. {
  764. ym310_ntp_sync_time();
  765. }
  766. }
  767. loop_counter++;
  768. if (loop_counter == 0)
  769. loop_counter = 1;
  770. rt_thread_mdelay(MAIN_LOOP_DELAY_MS);
  771. }
  772. ym310_gps_deinit();
  773. LOG_I("Maintenance thread stopped");
  774. }
  775. /*=============================================================================
  776. * Public API
  777. *============================================================================*/
  778. rt_thread_t ym310_start_maintain_thread(void)
  779. {
  780. g_maintain_thread = rt_thread_create("ym310_maint", ym310_maintain_thread_entry,
  781. RT_NULL, MAINTAIN_THREAD_STACK_SIZE,
  782. MAINTAIN_THREAD_PRIORITY, 10);
  783. if (g_maintain_thread == RT_NULL)
  784. {
  785. LOG_E("Failed to create maintenance thread!");
  786. return RT_NULL;
  787. }
  788. rt_thread_startup(g_maintain_thread);
  789. return g_maintain_thread;
  790. }
  791. void ym310_maintain_thread_stop(void)
  792. {
  793. s_maintain_thread_running = RT_FALSE;
  794. }
  795. /*=============================================================================
  796. * GCJ-02 Transform Test Command (MSH) - Integer Output Only
  797. *============================================================================*/
  798. static void transform_test(int argc, char **argv)
  799. {
  800. const char *lat_nmea, *ns, *lon_nmea, *ew;
  801. if (argc >= 5)
  802. {
  803. lon_nmea = argv[1];
  804. ew = argv[2];
  805. lat_nmea = argv[3];
  806. ns = argv[4];
  807. }
  808. else
  809. {
  810. lon_nmea = "11339.66602";
  811. ew = "E";
  812. lat_nmea = "3448.60878";
  813. ns = "N";
  814. rt_kprintf("[TEST] Using default coords: %s%s, %s%s\n", lon_nmea, ew, lat_nmea, ns);
  815. }
  816. /* Parse WGS84 for display */
  817. double wgs84_lat = 0.0, wgs84_lon = 0.0;
  818. char temp[16];
  819. strncpy(temp, lat_nmea, sizeof(temp) - 1);
  820. temp[sizeof(temp) - 1] = '\0';
  821. char *dot = strchr(temp, '.');
  822. if (dot)
  823. {
  824. char deg_str[3] = {0};
  825. strncpy(deg_str, temp, 2);
  826. int deg = atoi(deg_str);
  827. double minutes = atof(temp + 2);
  828. wgs84_lat = (double)deg + minutes / 60.0;
  829. if (*ns == 'S' || *ns == 's')
  830. wgs84_lat = -wgs84_lat;
  831. }
  832. strncpy(temp, lon_nmea, sizeof(temp) - 1);
  833. temp[sizeof(temp) - 1] = '\0';
  834. dot = strchr(temp, '.');
  835. if (dot)
  836. {
  837. char deg_str[4] = {0};
  838. strncpy(deg_str, temp, 3);
  839. int deg = atoi(deg_str);
  840. double minutes = atof(temp + 3);
  841. wgs84_lon = (double)deg + minutes / 60.0;
  842. if (*ew == 'W' || *ew == 'w')
  843. wgs84_lon = -wgs84_lon;
  844. }
  845. long wgs84_lat_int = (long)(wgs84_lat * 1e7 + (wgs84_lat >= 0 ? 0.5 : -0.5));
  846. long wgs84_lon_int = (long)(wgs84_lon * 1e7 + (wgs84_lon >= 0 ? 0.5 : -0.5));
  847. /* Convert to GCJ-02 */
  848. int gcj_lat_1e7, gcj_lon_1e7;
  849. nmea_pair_to_gcj02_int(lat_nmea, *ns, lon_nmea, *ew, &gcj_lat_1e7, &gcj_lon_1e7);
  850. /* Print results */
  851. rt_kprintf("\n========================================\n");
  852. rt_kprintf("[WGS-84] %ld.%07ld, %ld.%07ld\n",
  853. wgs84_lat_int / 10000000, (wgs84_lat_int % 10000000) >= 0 ? (wgs84_lat_int % 10000000) : -(wgs84_lat_int % 10000000),
  854. wgs84_lon_int / 10000000, (wgs84_lon_int % 10000000) >= 0 ? (wgs84_lon_int % 10000000) : -(wgs84_lon_int % 10000000));
  855. rt_kprintf("[GCJ-02] %d.%07d, %d.%07d\n",
  856. gcj_lat_1e7 / 10000000, (gcj_lat_1e7 % 10000000) >= 0 ? (gcj_lat_1e7 % 10000000) : -(gcj_lat_1e7 % 10000000),
  857. gcj_lon_1e7 / 10000000, (gcj_lon_1e7 % 10000000) >= 0 ? (gcj_lon_1e7 % 10000000) : -(gcj_lon_1e7 % 10000000));
  858. long dlat = (long)gcj_lat_1e7 - wgs84_lat_int;
  859. long dlon = (long)gcj_lon_1e7 - wgs84_lon_int;
  860. rt_kprintf("[OFFSET] dLat = %ld (1e-7 deg), dLon = %ld (1e-7 deg)\n", dlat, dlon);
  861. rt_kprintf("========================================\n\n");
  862. }
  863. MSH_CMD_EXPORT(transform_test, Test WGS84 to GCJ - 02 conversion)