title logo
title right side
19.10.2025 16:02:25 Lokal:
Serverstatus: (4.01/3.80/3.8651.88/1126.5 GByte  
Aussentemperatur : 14 °C  Luftdruck : 1016.10 hPas/mbar
Impressions: 8 
Visits heute: 382 
Go SSL ...Home  

GPS-Terminal-Visualisierer (NMEA-Tracker) V0.7

Kurzbeschreibung

Der GPS-Terminal-Visualisierer ist ein in C geschriebenes Werkzeug zur Echtzeit-Verarbeitung und Darstellung von NMEA-GPS-Daten direkt im Terminal. Die Anwendung nutzt die ncurses-Bibliothek, um eine dynamische, dreigeteilte Oberfläche zu schaffen, die nicht nur die aktuellen Positionsdaten anzeigt, sondern auch eine kartesische Darstellung der zurückgelegten Strecke (Track) bietet.

Dieses Tool ist ideal für die Überwachung und Diagnose seriell verbundener GPS/GNSS-Empfänger in Linux-Umgebungen (z.B. Raspberry Pi oder Server).

Funktionsumfang

Das Programm liest kontinuierlich NMEA-Sätze (wie $GPGGA, $GPRMC, etc.) von der Standardeingabe und teilt die Informationen in drei Fenster auf:

1. Hauptfenster (GPS-Daten)

Hier werden die wichtigsten Navigationsdaten übersichtlich dargestellt:

* Position: Hohe Genauigkeit von Breitengrad und Längengrad.

* Geschwindigkeit: Angezeigt in Knoten und umgerechnet in km/h.

* Höhe (m) und Genauigkeit (m).

* Status: Fix-Qualität, Fix-Typ (2D/3D) und genutztes GNSS-System (GPS, GLONASS, etc.).

* Zeit & Datum in UTC.

2. Track-Fenster (Kartesische Darstellung)

Dieses innovative Fenster speichert bis zu 512 Trackpunkte und stellt die Bewegung grafisch dar. Es berechnet dynamisch die Minimal- und Maximal-Koordinaten, um die aufgezeichneten Punkte so zu skalieren, dass sie in das Terminalfenster passen.

* Die aktuelle Position wird mit einem 'O' hervorgehoben, frühere Punkte mit einem '.'.

* Es berechnet und zeigt die Diagonale Distanz (in Metern) der aufgezeichneten Gesamtstrecke mithilfe der Haversine-Formel an.

3. Satellitenfenster

Bietet eine detaillierte Übersicht über die Signalqualität:

* Liste aller sichtbaren Satelliten.

* Wichtige Metriken: PRN-Nummer, Höhe, Azimut und Signal-Rausch-Verhältnis (SNR).

Debug-Funktionalität

Das Tool bietet eine einfache, aber mächtige Debug-Funktion zur Fehlersuche und Analyse der eingehenden NMEA-Daten: | Funktion | Konfigurations-Makro | Verhalten |- |- |- Debug-Modus (Aktiv) | Ändere #define DBG false auf #define DBG true | Das Track-Fenster wird zum Log-Fenster und zeigt alle eingehenden NMEA-Sätze in Echtzeit an. Pars-Fehler werden zusätzlich in die Datei debug.log geschrieben. Debug-Modus (Inaktiv) | #define DBG false | Das Track-Fenster wird für die grafische Streckenanzeige genutzt (Standardbetrieb).

Quellcode

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <math.h>
#include <ncurses.h>

// Struktur zum Speichern der GPS-Daten
typedef struct {
    double latitude;
    double longitude;
    double altitude;
    double speed_knots;
    int fix_quality;
    int satellites;
    char utc_time[11];
    char utc_date[7];
    char fix_mode[2];
    int fix_type;
    double pdop;
    double hdop;
    double vdop;
    int system;
    int visible_satellites;
    int satellite_prn[32];
    int satellite_elevation[32];
    int satellite_azimuth[32];
    int satellite_snr[32];
    double accuracy_meters; // Neues Feld für die Genauigkeit in Metern
} GpsData;

#define DBG false
#define DBG_FILE "debug.log"

// Definition des Erd-Radius in Metern (Mittelwert)
#define EARTH_RADIUS_METERS 6371000.0

#define TRACKPOINTS 512

typedef struct {
        double latitude[TRACKPOINTS];
        double longitude[TRACKPOINTS];
        double altitude[TRACKPOINTS];
} TrackData;

long trackpoint=0,trackpoint_max=0;

FILE* dbg_fp;

// Deklarationen
double calculate_distance(double lat1, double lon1, double lat2, double lon2);
void parse_gpgga(const char *nmea, GpsData *data);
void parse_gprmc(const char *nmea, GpsData *data);
void parse_gpgll(const char *nmea, GpsData *data);
void parse_gpgsa(const char *nmea, GpsData *data);
void parse_gpgsv(const char *nmea, GpsData *data);
void draw_ui(WINDOW *main_win, WINDOW *sat_win, const GpsData *data);
int data_changed(const GpsData *current, const GpsData *last);
void read_and_parse(GpsData *data, TrackData *my_track, WINDOW *main_win, WINDOW *sat_win, WINDOW *log_win);
void dbglog(char *data);

double calculate_distance(double lat1, double lon1, double lat2, double lon2) {
    // 1. Umrechnung von Grad in Radiant
    double lat1_rad = lat1 * (M_PI / 180.0);
    double lon1_rad = lon1 * (M_PI / 180.0);
    double lat2_rad = lat2 * (M_PI / 180.0);
    double lon2_rad = lon2 * (M_PI / 180.0);

    // 2. Berechnung der Differenzen
    double dlat = lat2_rad - lat1_rad;
    double dlon = lon2_rad - lon1_rad;

    // 3. Anwendung der Haversine-Formel
    double a = sin(dlat / 2) * sin(dlat / 2) +
               cos(lat1_rad) * cos(lat2_rad) *
               sin(dlon / 2) * sin(dlon / 2);

    double c = 2 * atan2(sqrt(a), sqrt(1 - a));

    // 4. Berechnung der endgültigen Distanz
    double distance = EARTH_RADIUS_METERS * c;

    return distance;
}

// Funktion, um zwei GpsData-Strukturen zu vergleichen
int data_changed(const GpsData *current, const GpsData *last) {
    if (current->latitude != last->latitude ||
        current->longitude != last->longitude ||
        current->altitude != last->altitude ||
        current->speed_knots != last->speed_knots ||
        current->fix_quality != last->fix_quality ||
        current->fix_type != last->fix_type ||
        current->satellites != last->satellites ||
        current->visible_satellites != last->visible_satellites ||
        strcmp(current->utc_time, last->utc_time) != 0 ||
        strcmp(current->utc_date, last->utc_date) != 0 ||
        current->hdop != last->hdop ||
        current->accuracy_meters != last->accuracy_meters) { // Neuer Vergleich
        return 1;
    }

    for (int i = 0; i < current->visible_satellites && i < 32; i++) {
        if (current->satellite_prn[i] != last->satellite_prn[i] ||
            current->satellite_snr[i] != last->satellite_snr[i]) {
            return 1;
        }
    }

    return 0;
}

// Funktion zum Initialisieren und Zeichnen der ncurses-Oberfläche
void draw_ui(WINDOW *main_win, WINDOW *sat_win, const GpsData *data) {
    wclear(main_win);
    wclear(sat_win);

    box(main_win, 0, 0);
    mvwprintw(main_win, 0, 1, "GPS Daten (%d)", data->system);
    mvwprintw(main_win, 2, 4, "Position:      Breitengrad: %.8f, Laengengrad: %.8f", data->latitude, data->longitude);
    mvwprintw(main_win, 3, 4, "Hoehe:         %.2f m", data->altitude);
    mvwprintw(main_win, 4, 4, "Geschw.:       %.1f km/h (%.2f Knoten)", data->speed_knots*1.852, data->speed_knots);
    mvwprintw(main_win, 5, 4, "Datum (UTC):   %.2s.%.2s.20%.2s", data->utc_date, data->utc_date + 2, data->utc_date + 4);
    mvwprintw(main_win, 6, 4, "Uhrzeit (UTC): %.2s:%.2s:%.2s", data->utc_time, data->utc_time + 2, data->utc_time + 4);
    mvwprintw(main_win, 7, 4, "Fix-Qualitaet: %d", data->fix_quality);
    mvwprintw(main_win, 8, 4, "Fix-Typ:       %s", (data->fix_type == 1) ? "Kein Fix" : (data->fix_type == 2) ? "2D Fix" : "3D Fix");
    mvwprintw(main_win, 9, 4, "Satelliten:    %d (verwendet), %d (sichtbar)", data->satellites, data->visible_satellites);
    mvwprintw(main_win, 10, 4, "HDOP:          %.2f", data->hdop);
    mvwprintw(main_win, 11, 4, "Genauigkeit:   ~%.2f m", data->hdop * 5.0); // Neue Ausgabe

    wrefresh(main_win);

    box(sat_win, 0, 0);
    mvwprintw(sat_win, 0, 1, "Sichtbare Satelliten");
    mvwprintw(sat_win, 2, 2, "PRN\tHoehe\tAzimut\tSNR");
    mvwprintw(sat_win, 3, 2, "-----------------------------------");

    for (int i = 0; i < data->visible_satellites && i < 32; i++) {
        mvwprintw(sat_win, 5 + i, 2, "%d\t%d\t%d\t%d",
                  data->satellite_prn[i],
                  data->satellite_elevation[i],
                  data->satellite_azimuth[i],
                  data->satellite_snr[i]);
    }

    wrefresh(sat_win);
}

// ... (Hier folgen die Parser-Funktionen, wie im vorherigen Beispiel) ...
void parse_gpgga(const char *nmea_sentence, GpsData *data) {
    char form, s_time[11], s_latitude[15], s_longitude[15], s_altitude[15], s_hdop[10], log[128];
    int fix_quality, satellites;
    double latitude, longitude, altitude, hdop;
    //                           1     2      3            4  5         6
    //                           0     1     2     3      4     5      6  7  8      9
    if (sscanf(nmea_sentence, "$G%cGGA,%[^,],%[^,],%*[^,],%[^,],%*[^,],%d,%d,%[^,],%[^,]",
               &form, s_time, s_latitude, s_longitude, &fix_quality, &satellites, s_hdop, s_altitude) == 8) {
        sprintf(log, "G%cGGA: %s-%s,%s^%s - %d %s #%d\n", form, s_time, s_latitude, s_longitude, s_altitude, fix_quality, s_hdop, satellites);
        dbglog(log);
        double lat_deg = strtod(s_latitude, NULL) / 100.0;
        double lat_min = fmod(lat_deg, 1.0) * 100.0 / 60.0;
        latitude = floor(lat_deg) + lat_min;
        if (strstr(nmea_sentence, ",S,") != NULL) latitude = -latitude;
        double lon_deg = strtod(s_longitude, NULL) / 100.0;
        double lon_min = fmod(lon_deg, 1.0) * 100.0 / 60.0;
        longitude = floor(lon_deg) + lon_min;
        if (strstr(nmea_sentence, ",W,") != NULL) longitude = -longitude;
        altitude = strtod(s_altitude, NULL);
        hdop = strtod(s_hdop, NULL);
        data->latitude = latitude;
        data->longitude = longitude;
        data->altitude = altitude;
        data->fix_quality = fix_quality;
        data->satellites = satellites;
        data->hdop = hdop;
        strcpy(data->utc_time, s_time);
    }
}

void parse_gprmc(const char *nmea_sentence, GpsData *data) {
    char form, s_speed[15], s_date[7], s_time[11], status, log[128];
    double speed;
    if (sscanf(nmea_sentence, "$G%cRMC,%[^,],%c,%*[^,],%*[^,],%*[^,],%*[^,],%[^,],%*[^,],%[^,]",
               &form, s_time, &status, s_speed, s_date) == 5) {
        sprintf(log, "G%cRMC: %s %s %s %c\n", form, s_speed, s_date, s_time, status);
        dbglog(log);
        if (status == 'A') {
            speed = strtod(s_speed, NULL);
            data->speed_knots = speed;
            strcpy(data->utc_time, s_time);
            strcpy(data->utc_date, s_date);
        }
    }
}

void parse_gpgll(const char *nmea_sentence, GpsData *data) {
    char form[15], s_latitude[15], s_longitude[15], s_time[11], status, log[128];
    double latitude, longitude;
    sprintf(log, "%s\n", nmea_sentence);
    dbglog(log);
    if (sscanf(nmea_sentence, "%[^,],%[^,],%*[^,],%[^,],%*[^,],%[^,],%c",
               form, s_latitude, s_longitude, s_time, &status) == 5) {
        sprintf(log, "G%cGLL: %s %s %s %c\n", form[2], s_time, s_latitude, s_longitude, status);
        dbglog(log);
        if (status == 'A') {
            double lat_deg = strtod(s_latitude, NULL) / 100.0;
            double lat_min = fmod(lat_deg, 1.0) * 100.0 / 60.0;
            latitude = floor(lat_deg) + lat_min;
            if (strstr(nmea_sentence, ",S,") != NULL) latitude = -latitude;
            double lon_deg = strtod(s_longitude, NULL) / 100.0;
            double lon_min = fmod(lon_deg, 1.0) * 100.0 / 60.0;
            longitude = floor(lon_deg) + lon_min;
            if (strstr(nmea_sentence, ",W,") != NULL) longitude = -longitude;
            data->latitude = latitude;
            data->longitude = longitude;
        }
        strcpy(data->utc_time, s_time);
    }
}

void parse_gpgsa(const char *nmea_sentence, GpsData *data) {
    char s_pdop[10], s_hdop[10], s_vdop[10], form;
    int fix_type, isystem;
    char fix_mode[2];
    if (sscanf(nmea_sentence, "$G%cGSA,%c,%d,%*[^,],%*[^,],%*[^,],%*[^,],%*[^,],%*[^,],%*[^,],%*[^,],%*[^,],%*[^,],%*[^,],%[^,],%[^,],%[^,],%d",
               &form, fix_mode, &fix_type, s_pdop, s_hdop, s_vdop, &isystem) == 7) {
        strcpy(data->fix_mode, fix_mode);
        data->fix_type = fix_type;
        data->pdop = strtod(s_pdop, NULL);
        data->hdop = strtod(s_hdop, NULL);
        data->vdop = strtod(s_vdop, NULL);
        data->system = isystem;
        // Berechnung der Genauigkeit in Metern
        if (data->hdop > 0) {
            data->accuracy_meters = data->hdop * 5.0;
        } else {
            data->accuracy_meters = -1.0;
        }
    }
}

void parse_gpgsv(const char *nmea_sentence, GpsData *data) {
    int total_sentences, sentence_number, visible_satellites;
    char *token;
    char temp_line[1024];

    strcpy(temp_line, nmea_sentence);

    token = strtok(temp_line, ",");
    if (token == NULL) return;
    total_sentences = atoi(strtok(NULL, ","));
    sentence_number = atoi(strtok(NULL, ","));
    visible_satellites = atoi(strtok(NULL, ","));

    if (sentence_number == 1) {
        data->visible_satellites = visible_satellites;
        memset(data->satellite_prn, 0, sizeof(data->satellite_prn));
        memset(data->satellite_elevation, 0, sizeof(data->satellite_elevation));
        memset(data->satellite_azimuth, 0, sizeof(data->satellite_azimuth));
        memset(data->satellite_snr, 0, sizeof(data->satellite_snr));
    }

    for (int i = 0; i < 4; i++) {
        int index = (sentence_number - 1) * 4 + i;
        if (index >= 32) break;

        char *prn_str = strtok(NULL, ",");
        char *elev_str = strtok(NULL, ",");
        char *azim_str = strtok(NULL, ",");
        char *snr_str = strtok(NULL, "*");

        if (prn_str && elev_str && azim_str && snr_str) {
            data->satellite_prn[index] = atoi(prn_str);
            data->satellite_elevation[index] = atoi(elev_str);
            data->satellite_azimuth[index] = atoi(azim_str);
            data->satellite_snr[index] = atoi(snr_str);
        }
    }
}

void read_and_parse(GpsData *data, TrackData *my_track, WINDOW *main_win, WINDOW *sat_win, WINDOW *log_win) {
    char line[1024];
    int line_cnt=0,max_x,max_y,ax,ay,wx,wy,wz;
    double max_coord_x=-1000, min_coord_x=1000, max_coord_y=-1000, min_coord_y=1000, max_coord_z=-100000, min_coord_z=100000;
    GpsData last_gps_data = {0};
    while (fgets(line, sizeof(line), stdin) != NULL) {
        line[strcspn(line, "\n")] = 0;
        if ((strstr(line, "$GPGGA") == line) || (strstr(line, "$GNGGA") == line) || (strstr(line, "$GLGGA") == line)) {
            parse_gpgga(line, data);
                        if (!DBG) {
                        if (trackpoint_max<trackpoint) { trackpoint_max=trackpoint; }
                        wclear(log_win);
                        getmaxyx(log_win, max_y, max_x);
                        max_x = max_x -2;
                        max_y = max_y -2;
                        max_coord_x=-1000;
                        min_coord_x=1000;
                        max_coord_y=-1000;
                        min_coord_y=1000;
                        max_coord_z=-100000;
                        min_coord_z=100000;
                        for (long t=0;t<trackpoint_max;t++)
                        {
                                if (my_track->latitude[t]>max_coord_x) { max_coord_x = my_track->latitude[t]; }
                                if (my_track->latitude[t]<min_coord_x) { min_coord_x = my_track->latitude[t]; }
                                if (my_track->longitude[t]>max_coord_y) { max_coord_y = my_track->longitude[t]; }
                                if (my_track->longitude[t]<min_coord_y) { min_coord_y = my_track->longitude[t]; }
                                if (my_track->altitude[t]>max_coord_z) { max_coord_z = my_track->altitude[t]; }
                                if (my_track->altitude[t]<min_coord_z) { min_coord_z = my_track->altitude[t]; }
                        }
                        for (long t=0;t<trackpoint_max;t++)
                        {
                                        wx = (int) ((my_track->latitude[t]-min_coord_x)/(max_coord_x-min_coord_x) * (double) max_x );
                                        wy = (int) ((my_track->longitude[t]-min_coord_y)/(max_coord_y-min_coord_y) * (double) max_y );
                                        wz = (int) ((my_track->altitude[t]-min_coord_z)/(max_coord_z-min_coord_z));
                                        if ((t!=trackpoint) && (trackpoint_max!=TRACKPOINTS))
                                        {
                                                mvwprintw(log_win,wy+1,wx+1,".");
                                        } else {
                                                ax=wx;
                                                ay=wy;
                                        }
                                }
                                mvwprintw(log_win,ay,ax,"O");
                        box(log_win, 0, 0);
                        mvwprintw(log_win,0,1,"Track: MXX %.5f MNX %.5f  MXY %.5f MNY %.5f [%ld/%ld]",
                                max_coord_x,
                                min_coord_x,
                                max_coord_y,
                                min_coord_y,
                                trackpoint,
                                trackpoint_max);
                        mvwprintw(log_win,21,2,"Diagonale: %.1f m",calculate_distance(min_coord_x, min_coord_y, max_coord_x, max_coord_y));
                        wrefresh(log_win);
                                my_track->latitude[trackpoint] = data->latitude;
                                my_track->longitude[trackpoint] = data->longitude;
                                my_track->altitude[trackpoint] = data->altitude;
                                trackpoint = (trackpoint + 1) % TRACKPOINTS;
                }
        } else if ((strstr(line, "$GPRMC") == line) || (strstr(line, "$GNRMC") == line) || (strstr(line, "$GLRMC") == line)) {
            parse_gprmc(line, data);
        } else if ((strstr(line, "$GNGLL") == line) || (strstr(line, "$GPGLL") == line) || (strstr(line, "$GLGLL") == line)) {
            parse_gpgll(line, data);
        } else if ((strstr(line, "$GPGSA") == line) || (strstr(line, "$GNGSA") == line) || (strstr(line, "$GLGSA") == line)) {
            parse_gpgsa(line, data);
        } else if ((strstr(line, "$GPGSV") == line) || (strstr(line, "$GNGSV") == line) || (strstr(line, "$GLGSV") == line)) {
            parse_gpgsv(line, data);
        }
        if (data_changed(data, &last_gps_data)) {
            draw_ui(main_win, sat_win, data);
                        if (DBG) {
                                if ((line_cnt % 19) == 0) { wclear(log_win); }
                wprintw(log_win,"%s\n",line);
                line_cnt++;
                        box(log_win, 0, 0);
                        wrefresh(log_win);
                }
            doupdate();
            last_gps_data = *data;
        }
    }
}
void dbglog(char *data)
{
        fprintf(dbg_fp, "%s", data);
    fflush(dbg_fp);
}

int main(void) {
    GpsData my_gps_data;
        TrackData my_track;
    dbg_fp = fopen(DBG_FILE, "a");
    memset(&my_gps_data, 0, sizeof(my_gps_data));
    initscr();
    cbreak();
    noecho();
    curs_set(0);
    int main_win_height = 13, main_win_width = 120;
    int sat_win_height = 22, sat_win_width = 40;
    int log_win_height = 22, log_win_width = 80;
    WINDOW *main_win = newwin(main_win_height, main_win_width, 0, 0);
    WINDOW *sat_win = newwin(sat_win_height, sat_win_width, main_win_height, 0);
    WINDOW *log_win = newwin(log_win_height, log_win_width, main_win_height, sat_win_width);
    mvwprintw(main_win, 1, 2, "Warte auf NMEA-Daten...");
    mvwprintw(main_win, 2, 2, "Zum Beenden Strg+C druecken.");
    my_gps_data.system = -1;
    box(main_win, 0, 0);
    wrefresh(main_win);
    read_and_parse(&my_gps_data, &my_track, main_win, sat_win, log_win);
    delwin(main_win);
    delwin(sat_win);
    delwin(log_win);
    endwin();
    if (dbg_fp) fclose(dbg_fp);
    return 0;
}

Lizenz

GPL V3 oder neuer

Download

Format tar.xz LINK
Format zip LINK