/* * GPS Test Tool - PC Application * * Tests GPS filtering and GPX generation using actual embedded code * Usage: ./gps-test-tool * * Generates: * - output_filtered.gpx * - output_unfiltered.gpx * - debug_filtered.txt * - debug_unfiltered.txt */ #define PC_BUILD #include "main.h" /* Global variables expected by project code */ volatile struct system_s System; struct location_s location; time_t utc; unsigned char config_flags = 0; unsigned char FLAGS = 0; volatile unsigned char reset_cause = 0; /* no real MCU reset happened; log_boot_record() reports "UNKNOWN" */ /* File handle for debug output redirection */ FILE *debug_output = NULL; /* soft/syslog.c's log_config()/log_boot_record() print these; not part of * this PC build otherwise (they're normally in soft/settings.c). */ __flash const char gnss_name_stub[] = "GPS+GLONASS+GALILEO/etc (PC test tool - not tracked per mode)"; __flash const char *gnss_names[] = { gnss_name_stub, gnss_name_stub, gnss_name_stub, gnss_name_stub, gnss_name_stub, gnss_name_stub, }; __flash const char uart_log_mode_stub[] = "n/a (PC test tool)"; __flash const char *uart_log_mode_names[] = { uart_log_mode_stub, uart_log_mode_stub, uart_log_mode_stub, }; /* Function prototypes from project files */ unsigned char gpx_init(FIL *file); unsigned char gpx_close(FIL *file); void gpx_process_point(struct location_s *loc, FIL *file); void gpx_reset_gap(void); time_t gps_parse(const char *nmea); /* Time function implementation */ char *get_iso_time(time_t time, unsigned char local) { static char output[32]; struct tm *ct = gmtime(&time); sprintf(output, "%04d-%02d-%02dT%02d:%02d:%02d.000Z", ct->tm_year + 1900, ct->tm_mon + 1, ct->tm_mday, ct->tm_hour, ct->tm_min, ct->tm_sec); return output; } void iso_time_to_filename(char *time) { while (*time) { switch (*time) { case ':': *time = '-'; break; case '+': *time = 'p'; break; } time++; } } /* Reset system state */ void reset_system(void) { memset((void*)&System, 0, sizeof(System)); memset(&location, 0, sizeof(location)); System.conf.skip_points = 0; /* Don't skip points for testing */ /* Match soft/settings.c's defaults_u8, so this tool's auto-pause behavior * (enabled below) matches an out-of-the-box device. */ System.conf.auto_pause_time = 30; System.conf.auto_pause_dist = 10; System.conf.auto_pause_speed = 3; System.conf.min_sats = 4; /* Require at least 4 satellites */ System.tracking_paused = 0; System.tracking_auto_paused = 0; System.bat_volt = 4.0f; /* no real ADC here; keeps the status line's v=/bat%= fields sane */ System.bat_volt_min = 4.0f; utc = 0; } /* Process NMEA file with specified filter setting */ int process_nmea_file(const char *input_file, const char *output_gpx, const char *debug_file, int filters_enabled) { FILE *input; FIL gpx_file; char line[256]; int point_count = 0; /* Open input file */ input = fopen(input_file, "r"); if (!input) { fprintf(stderr, "Error: Cannot open input file '%s'\n", input_file); return 1; } /* Open debug output file */ debug_output = fopen(debug_file, "w"); if (!debug_output) { fprintf(stderr, "Error: Cannot open debug file '%s'\n", debug_file); fclose(input); return 1; } /* Configure filters */ config_flags = (filters_enabled ? 0 : CONFFLAG_DISABLE_FILTERS) | CONFFLAG_AUTO_PAUSE; /* Reset system state */ reset_system(); gps_powered_on(); log_boot_record(); /* Open GPX output file */ if (f_open(&gpx_file, output_gpx, FA_WRITE | FA_OPEN_ALWAYS) != 0) { fprintf(stderr, "Error: Cannot open GPX file '%s'\n", output_gpx); fclose(input); fclose(debug_output); return 1; } /* Initialize GPX file */ if (gpx_init(&gpx_file) != 0) { fprintf(stderr, "Error: GPX initialization failed\n"); fclose(input); fclose(debug_output); f_close(&gpx_file); return 1; } fprintf(debug_output, "=== GPS Test Tool - %s ===\n", filters_enabled ? "FILTERS ENABLED" : "FILTERS DISABLED"); fprintf(debug_output, "Input: %s\n", input_file); fprintf(debug_output, "Output: %s\n\n", output_gpx); /* Process NMEA file line by line */ /* Kept in sync with soft/main.c's FIX_HOLD_EPOCHS. */ #define FIX_HOLD_EPOCHS 5 unsigned char fix_ok_prev = 0; unsigned char fix_hold_counter = 0; unsigned char dgps_prev = 0xFF; /* 0xFF: not yet known, suppress the first (non-)transition */ time_t next_status_utc = 0; time_t next_summary_utc = 0; while (fgets(line, sizeof(line), input)) { size_t len = strlen(line); /* Skip empty lines */ if (len == 0) continue; /* Ensure line ends with \r\n as expected by gps_parse */ if (len > 0 && line[len-1] == '\n') { line[len-1] = '\0'; len--; } if (len > 0 && line[len-1] == '\r') { line[len-1] = '\0'; len--; } /* Add back \r\n for gps_parse, bounded: fgets can fill line[] to * capacity with no trailing newline, leaving no room for it. */ if (len + 2 >= sizeof(line)) len = sizeof(line) - 3; line[len] = '\0'; strcat(line, "\r\n"); /* Parse NMEA sentence */ if (gps_parse(line) != 0) { /* Valid location data received (RMC parsed) */ /* Only process if we have valid coordinates from GGA */ if (System.location_valid == LOC_VALID_NEW || System.location_valid == LOC_VALID) { if (fix_gap_detected(location.time)) { auto_pause_reset_gap(); gpx_reset_gap(); } auto_pause_process(); gpx_process_point(&location, &gpx_file); point_count++; } } /* Mirror soft/main.c's system-log event/status triggers, so this * tool exercises and lets you review the same log output the * firmware produces. There's no real clock here, so pace the * periodic status line/session summary off the NMEA timestamps * instead of uptime. */ if (utc) { if (nmea_epoch_complete()) { unsigned char fix_ok_now = (FLAGS & F_GPSOK) && !System.sat_count_low; if (fix_ok_now == fix_ok_prev) { fix_hold_counter = 0; } else if (++fix_hold_counter >= FIX_HOLD_EPOCHS) { fix_hold_counter = 0; fix_ok_prev = fix_ok_now; if (fix_ok_now) log_fix_event(); else log_fix_lost(); } } if (System.dgps != dgps_prev) { if (dgps_prev != 0xFF) log_dgps_transition(); dgps_prev = System.dgps; } if (!next_status_utc) next_status_utc = utc + 60; if (utc >= next_status_utc) { log_status_line(); next_status_utc = utc + 60; } if (!next_summary_utc) next_summary_utc = utc + 3600; if (utc >= next_summary_utc) { log_session_summary(0); next_summary_utc = utc + 3600; } } } /* Close GPX file */ gpx_close(&gpx_file); log_session_summary(1); /* Print summary */ { unsigned int moving = get_logging_time(); unsigned long int stopped = get_pause_time(); /* points_accepted counts every point past all reject checks, at * AVG_COUNT=3 samples per written trkpt with filters enabled (1:1 * with filters disabled, so this is 0 in that mode). */ unsigned long int averaged_out = System.points_accepted - System.points_written * (filters_enabled ? 3UL : 1UL); fprintf(debug_output, "\n=== Summary ===\n"); fprintf(debug_output, "Points processed: %d\n", point_count); fprintf(debug_output, "Points during pauses: %lu\n", System.points_paused); fprintf(debug_output, "Points skipped (warm-up): %lu\n", System.points_skipped); fprintf(debug_output, "Points rejected: %lu\n", System.rejected_count); fprintf(debug_output, "Points averaged out: %lu\n", averaged_out); fprintf(debug_output, "Points saved: %lu\n", System.points_written); fprintf(debug_output, "Total distance: %.2f km\n", System.distance / 100000.0); fprintf(debug_output, "Elevation gain: %.1f m\n", System.elevation_gain / 10.0); fprintf(debug_output, "Elevation loss: %.1f m\n", System.elevation_loss / 10.0); fprintf(debug_output, "Moving time: %uh%02um\n", moving/3600, (moving/60)%60); fprintf(debug_output, "Stopped time: %luh%02lum\n", stopped/3600, (stopped/60)%60); fprintf(debug_output, "Average speed: %.2f km/h\n", get_avg_speed_x100()/100.0); fprintf(debug_output, "Average speed (dist/time): %.2f km/h\n", get_dist_avg_speed_x100()/100.0); } /* Close files */ fclose(input); fclose(debug_output); debug_output = NULL; printf("Processed %d points -> %s (filters %s)\n", point_count, output_gpx, filters_enabled ? "ON" : "OFF"); return 0; } int main(int argc, char *argv[]) { int ret; printf("GPS Test Tool v1.0\n"); printf("==================\n\n"); if (argc != 2) { fprintf(stderr, "Usage: %s \n", argv[0]); fprintf(stderr, "\nGenerates:\n"); fprintf(stderr, " - output_filtered.gpx\n"); fprintf(stderr, " - output_unfiltered.gpx\n"); fprintf(stderr, " - debug_filtered.txt\n"); fprintf(stderr, " - debug_unfiltered.txt\n"); return 1; } /* Process with filters enabled */ printf("Processing with filters ENABLED...\n"); ret = process_nmea_file(argv[1], "output_filtered.gpx", "debug_filtered.txt", 1); if (ret != 0) { fprintf(stderr, "Failed to process with filters enabled\n"); return ret; } /* Process with filters disabled */ printf("Processing with filters DISABLED...\n"); ret = process_nmea_file(argv[1], "output_unfiltered.gpx", "debug_unfiltered.txt", 0); if (ret != 0) { fprintf(stderr, "Failed to process with filters disabled\n"); return ret; } printf("\nProcessing complete!\n"); printf("Generated files:\n"); printf(" - output_filtered.gpx (filters ON)\n"); printf(" - output_unfiltered.gpx (filters OFF)\n"); printf(" - debug_filtered.txt (debug output with filters)\n"); printf(" - debug_unfiltered.txt (debug output without filters)\n"); return 0; }