Visits heute: 374
Aktuelle Seite: GPS-Terminal-Visualisierer (NMEA-Tracker) V0.7
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 |