main.c 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366
  1. /*
  2. * GPS Test Tool - PC Application
  3. *
  4. * Tests GPS filtering and GPX generation using actual embedded code
  5. * Usage: ./gps-test-tool [options] <input.nmea>
  6. *
  7. * Generates (default mode):
  8. * - output_filtered.gpx
  9. * - output_unfiltered.gpx
  10. * - debug_filtered.txt
  11. * - debug_unfiltered.txt
  12. *
  13. * With -o, does a single run with the given settings instead (used by
  14. * gps-test-tool-gui).
  15. */
  16. #define PC_BUILD
  17. #include "main.h"
  18. #include <getopt.h>
  19. /* Global variables expected by project code */
  20. volatile struct system_s System;
  21. struct location_s location;
  22. time_t utc;
  23. unsigned char FLAGS = 0;
  24. volatile unsigned char reset_cause = 0; /* no real MCU reset happened; log_boot_record() reports "UNKNOWN" */
  25. /* File handle for debug output redirection */
  26. FILE *debug_output = NULL;
  27. /* soft/syslog.c's log_config()/log_boot_record() print these; not part of
  28. * this PC build otherwise (they're normally in soft/settings.c). */
  29. __flash const char gnss_name_stub[] = "GPS+GLONASS+GALILEO/etc (PC test tool - not tracked per mode)";
  30. __flash const char *gnss_names[] = {
  31. gnss_name_stub, gnss_name_stub, gnss_name_stub,
  32. gnss_name_stub, gnss_name_stub, gnss_name_stub,
  33. };
  34. __flash const char uart_log_mode_stub[] = "n/a (PC test tool)";
  35. __flash const char *uart_log_mode_names[] = {
  36. uart_log_mode_stub, uart_log_mode_stub, uart_log_mode_stub,
  37. };
  38. /* Function prototypes from project files */
  39. unsigned char gpx_init(FIL *file);
  40. unsigned char gpx_close(FIL *file);
  41. void gpx_process_point(struct location_s *loc, FIL *file);
  42. void gpx_reset_gap(void);
  43. time_t gps_parse(const char *nmea);
  44. /* Time function implementation */
  45. char *get_iso_time(time_t time, unsigned char local) {
  46. static char output[32];
  47. struct tm *ct = gmtime(&time);
  48. sprintf(output, "%04d-%02d-%02dT%02d:%02d:%02d.000Z",
  49. ct->tm_year + 1900, ct->tm_mon + 1, ct->tm_mday,
  50. ct->tm_hour, ct->tm_min, ct->tm_sec);
  51. return output;
  52. }
  53. void iso_time_to_filename(char *time) {
  54. while (*time) {
  55. switch (*time) {
  56. case ':': *time = '-'; break;
  57. case '+': *time = 'p'; break;
  58. }
  59. time++;
  60. }
  61. }
  62. /* Settings applied by reset_system(); command-line options override these. */
  63. static unsigned char opt_skip_points = 0; /* Don't skip points for testing */
  64. /* Match soft/settings.c's defaults_u8, so this tool's auto-pause behavior
  65. * matches an out-of-the-box device. */
  66. static unsigned char opt_auto_pause_time = 30;
  67. static unsigned char opt_auto_pause_dist = 10;
  68. static unsigned char opt_auto_pause_speed = 3;
  69. static unsigned char opt_min_sats = 4; /* Require at least 4 satellites */
  70. static unsigned char opt_auto_pause = 1;
  71. static unsigned char opt_verbose_log = 0;
  72. /* Reset system state */
  73. void reset_system(void) {
  74. memset((void*)&System, 0, sizeof(System));
  75. memset(&location, 0, sizeof(location));
  76. System.conf.skip_points = opt_skip_points;
  77. System.conf.auto_pause_time = opt_auto_pause_time;
  78. System.conf.auto_pause_dist = opt_auto_pause_dist;
  79. System.conf.auto_pause_speed = opt_auto_pause_speed;
  80. System.conf.min_sats = opt_min_sats;
  81. System.tracking_paused = 0;
  82. System.tracking_auto_paused = 0;
  83. System.bat_volt = 4.0f; /* no real ADC here; keeps the status line's v=/bat%= fields sane */
  84. System.bat_volt_min = 4.0f;
  85. utc = 0;
  86. }
  87. /* Process NMEA file with specified filter setting */
  88. int process_nmea_file(const char *input_file, const char *output_gpx,
  89. const char *debug_file, int filters_enabled) {
  90. FILE *input;
  91. FIL gpx_file;
  92. char line[256];
  93. int point_count = 0;
  94. /* Open input file */
  95. input = fopen(input_file, "r");
  96. if (!input) {
  97. fprintf(stderr, "Error: Cannot open input file '%s'\n", input_file);
  98. return 1;
  99. }
  100. /* Open debug output file */
  101. debug_output = fopen(debug_file, "w");
  102. if (!debug_output) {
  103. fprintf(stderr, "Error: Cannot open debug file '%s'\n", debug_file);
  104. fclose(input);
  105. return 1;
  106. }
  107. /* Reset system state */
  108. reset_system();
  109. /* Configure filters */
  110. set_flag(CONFFLAG_DISABLE_FILTERS, !filters_enabled);
  111. set_flag(CONFFLAG_AUTO_PAUSE, opt_auto_pause);
  112. set_flag(CONFFLAG_VERBOSE_LOG, opt_verbose_log);
  113. gps_powered_on();
  114. log_boot_record();
  115. /* Open GPX output file */
  116. if (f_open(&gpx_file, output_gpx, FA_WRITE | FA_OPEN_ALWAYS) != 0) {
  117. fprintf(stderr, "Error: Cannot open GPX file '%s'\n", output_gpx);
  118. fclose(input);
  119. fclose(debug_output);
  120. return 1;
  121. }
  122. /* Initialize GPX file */
  123. if (gpx_init(&gpx_file) != 0) {
  124. fprintf(stderr, "Error: GPX initialization failed\n");
  125. fclose(input);
  126. fclose(debug_output);
  127. f_close(&gpx_file);
  128. return 1;
  129. }
  130. fprintf(debug_output, "=== GPS Test Tool - %s ===\n",
  131. filters_enabled ? "FILTERS ENABLED" : "FILTERS DISABLED");
  132. fprintf(debug_output, "Input: %s\n", input_file);
  133. fprintf(debug_output, "Output: %s\n\n", output_gpx);
  134. /* Process NMEA file line by line */
  135. /* Kept in sync with soft/main.c's FIX_HOLD_EPOCHS. */
  136. #define FIX_HOLD_EPOCHS 5
  137. unsigned char fix_ok_prev = 0;
  138. unsigned char fix_hold_counter = 0;
  139. unsigned char dgps_prev = 0xFF; /* 0xFF: not yet known, suppress the first (non-)transition */
  140. time_t next_status_utc = 0;
  141. time_t next_summary_utc = 0;
  142. while (fgets(line, sizeof(line), input)) {
  143. size_t len = strlen(line);
  144. /* Skip empty lines */
  145. if (len == 0) continue;
  146. /* Ensure line ends with \r\n as expected by gps_parse */
  147. if (len > 0 && line[len-1] == '\n') {
  148. line[len-1] = '\0';
  149. len--;
  150. }
  151. if (len > 0 && line[len-1] == '\r') {
  152. line[len-1] = '\0';
  153. len--;
  154. }
  155. /* Add back \r\n for gps_parse, bounded: fgets can fill line[] to
  156. * capacity with no trailing newline, leaving no room for it. */
  157. if (len + 2 >= sizeof(line))
  158. len = sizeof(line) - 3;
  159. line[len] = '\0';
  160. strcat(line, "\r\n");
  161. /* Parse NMEA sentence */
  162. if (gps_parse(line) != 0) {
  163. /* Valid location data received (RMC parsed) */
  164. /* Only process if we have valid coordinates from GGA */
  165. if (System.location_valid == LOC_VALID_NEW || System.location_valid == LOC_VALID) {
  166. if (fix_gap_detected(location.time)) {
  167. auto_pause_reset_gap();
  168. gpx_reset_gap();
  169. }
  170. auto_pause_process();
  171. gpx_process_point(&location, &gpx_file);
  172. point_count++;
  173. }
  174. }
  175. /* Mirror soft/main.c's system-log event/status triggers, so this
  176. * tool exercises and lets you review the same log output the
  177. * firmware produces. There's no real clock here, so pace the
  178. * periodic status line/session summary off the NMEA timestamps
  179. * instead of uptime. */
  180. if (utc) {
  181. if (nmea_epoch_complete()) {
  182. unsigned char fix_ok_now = (FLAGS & F_GPSOK) && !System.sat_count_low;
  183. if (fix_ok_now == fix_ok_prev) {
  184. fix_hold_counter = 0;
  185. } else if (++fix_hold_counter >= FIX_HOLD_EPOCHS) {
  186. fix_hold_counter = 0;
  187. fix_ok_prev = fix_ok_now;
  188. if (fix_ok_now)
  189. log_fix_event();
  190. else
  191. log_fix_lost();
  192. }
  193. }
  194. if (System.dgps != dgps_prev) {
  195. if (dgps_prev != 0xFF)
  196. log_dgps_transition();
  197. dgps_prev = System.dgps;
  198. }
  199. if (!next_status_utc)
  200. next_status_utc = utc + 60;
  201. if (utc >= next_status_utc) {
  202. log_status_line();
  203. next_status_utc = utc + 60;
  204. }
  205. if (!next_summary_utc)
  206. next_summary_utc = utc + 3600;
  207. if (utc >= next_summary_utc) {
  208. log_session_summary(0);
  209. next_summary_utc = utc + 3600;
  210. }
  211. }
  212. }
  213. /* Close GPX file */
  214. gpx_close(&gpx_file);
  215. log_session_summary(1);
  216. /* Print summary */
  217. {
  218. unsigned int moving = get_logging_time();
  219. unsigned long int stopped = get_pause_time();
  220. /* points_accepted counts every point past all reject checks, at
  221. * AVG_COUNT=3 samples per written trkpt with filters enabled (1:1
  222. * with filters disabled, so this is 0 in that mode). */
  223. unsigned long int averaged_out = System.points_accepted - System.points_written *
  224. (filters_enabled ? 3UL : 1UL);
  225. fprintf(debug_output, "\n=== Summary ===\n");
  226. fprintf(debug_output, "Points processed: %d\n", point_count);
  227. fprintf(debug_output, "Points during pauses: %lu\n", System.points_paused);
  228. fprintf(debug_output, "Points skipped (warm-up): %lu\n", System.points_skipped);
  229. fprintf(debug_output, "Points rejected: %lu\n", System.rejected_count);
  230. fprintf(debug_output, "Points averaged out: %lu\n", averaged_out);
  231. fprintf(debug_output, "Points saved: %lu\n", System.points_written);
  232. fprintf(debug_output, "Total distance: %.2f km\n", System.distance / 100000.0);
  233. fprintf(debug_output, "Elevation gain: %.1f m\n", System.elevation_gain / 10.0);
  234. fprintf(debug_output, "Elevation loss: %.1f m\n", System.elevation_loss / 10.0);
  235. fprintf(debug_output, "Moving time: %uh%02um\n", moving/3600, (moving/60)%60);
  236. fprintf(debug_output, "Stopped time: %luh%02lum\n", stopped/3600, (stopped/60)%60);
  237. fprintf(debug_output, "Average speed: %.2f km/h\n", get_avg_speed_x100()/100.0);
  238. fprintf(debug_output, "Average speed (dist/time): %.2f km/h\n", get_dist_avg_speed_x100()/100.0);
  239. }
  240. /* Close files */
  241. fclose(input);
  242. fclose(debug_output);
  243. debug_output = NULL;
  244. printf("Processed %d points -> %s (filters %s)\n",
  245. point_count, output_gpx, filters_enabled ? "ON" : "OFF");
  246. return 0;
  247. }
  248. static void usage(const char *name) {
  249. fprintf(stderr, "Usage: %s [options] <input.nmea>\n", name);
  250. fprintf(stderr, "\nWithout -o, generates:\n");
  251. fprintf(stderr, " - output_filtered.gpx\n");
  252. fprintf(stderr, " - output_unfiltered.gpx\n");
  253. fprintf(stderr, " - debug_filtered.txt\n");
  254. fprintf(stderr, " - debug_unfiltered.txt\n");
  255. fprintf(stderr, "\nOptions:\n");
  256. fprintf(stderr, " -o FILE single run: write GPX to FILE only\n");
  257. fprintf(stderr, " -l FILE single run: write debug output to FILE (default: none)\n");
  258. fprintf(stderr, " -n single run: disable filters\n");
  259. fprintf(stderr, " -A disable auto-pause\n");
  260. fprintf(stderr, " -t SEC auto-pause time (default %u)\n", opt_auto_pause_time);
  261. fprintf(stderr, " -d M auto-pause distance (default %u)\n", opt_auto_pause_dist);
  262. fprintf(stderr, " -s KMH auto-pause speed (default %u)\n", opt_auto_pause_speed);
  263. fprintf(stderr, " -k N skip points after fix (default %u)\n", opt_skip_points);
  264. fprintf(stderr, " -m N minimum satellites (default %u)\n", opt_min_sats);
  265. fprintf(stderr, " -v verbose log\n");
  266. }
  267. /* Parse a 0..255 setting value; returns 0 on success. */
  268. static int parse_u8(const char *str, unsigned char *val) {
  269. char *end;
  270. long v = strtol(str, &end, 10);
  271. if (!*str || *end || v < 0 || v > 255)
  272. return 1;
  273. *val = v;
  274. return 0;
  275. }
  276. int main(int argc, char *argv[]) {
  277. int ret, c, bad = 0;
  278. const char *single_gpx = NULL;
  279. const char *single_debug = "/dev/null";
  280. int single_filters = 1;
  281. while ((c = getopt(argc, argv, "o:l:nAt:d:s:k:m:v")) != -1) {
  282. switch (c) {
  283. case 'o': single_gpx = optarg; break;
  284. case 'l': single_debug = optarg; break;
  285. case 'n': single_filters = 0; break;
  286. case 'A': opt_auto_pause = 0; break;
  287. case 't': bad |= parse_u8(optarg, &opt_auto_pause_time); break;
  288. case 'd': bad |= parse_u8(optarg, &opt_auto_pause_dist); break;
  289. case 's': bad |= parse_u8(optarg, &opt_auto_pause_speed); break;
  290. case 'k': bad |= parse_u8(optarg, &opt_skip_points); break;
  291. case 'm': bad |= parse_u8(optarg, &opt_min_sats); break;
  292. case 'v': opt_verbose_log = 1; break;
  293. default: bad = 1; break;
  294. }
  295. }
  296. if (bad || optind != argc - 1) {
  297. usage(argv[0]);
  298. return 1;
  299. }
  300. if (single_gpx)
  301. return process_nmea_file(argv[optind], single_gpx, single_debug, single_filters);
  302. printf("GPS Test Tool v1.0\n");
  303. printf("==================\n\n");
  304. /* Process with filters enabled */
  305. printf("Processing with filters ENABLED...\n");
  306. ret = process_nmea_file(argv[optind], "output_filtered.gpx",
  307. "debug_filtered.txt", 1);
  308. if (ret != 0) {
  309. fprintf(stderr, "Failed to process with filters enabled\n");
  310. return ret;
  311. }
  312. /* Process with filters disabled */
  313. printf("Processing with filters DISABLED...\n");
  314. ret = process_nmea_file(argv[optind], "output_unfiltered.gpx",
  315. "debug_unfiltered.txt", 0);
  316. if (ret != 0) {
  317. fprintf(stderr, "Failed to process with filters disabled\n");
  318. return ret;
  319. }
  320. printf("\nProcessing complete!\n");
  321. printf("Generated files:\n");
  322. printf(" - output_filtered.gpx (filters ON)\n");
  323. printf(" - output_unfiltered.gpx (filters OFF)\n");
  324. printf(" - debug_filtered.txt (debug output with filters)\n");
  325. printf(" - debug_unfiltered.txt (debug output without filters)\n");
  326. return 0;
  327. }