Protokół NMEA i opis wiadomości
Spis treści
A protokół jest zbiorem reguł definiujących sposób formatowania, przesyłania i interpretowania danych pomiędzy dwoma lub większą liczbą urządzeń, tak aby mogły się one wzajemnie rozumieć.
Wyobraźmy to sobie jak język o ścisłych regułach gramatycznych – zarówno nadawca, jak i odbiorca muszą przestrzegać dokładnie tych samych zasad, w przeciwnym razie wiadomość jest pozbawiona sensu. Bez protokołów każdy producent wymyśliłby własny format, a urządzenia różnych marek nie mogłyby się ze sobą komunikować.
Protokół NMEA-0183 (od teraz NMEA) jest standardem branżowym dla technologii GNSS.
Przygotowaliśmy tę stronę jako źródło informacji o definicji protokołu NMEA oraz opis najpopularniejszych komunikatów. Jeśli przegapisz któryś komunikat lub znajdziesz literówkę, skontaktuj się z nami i naprawimy to 🙂
Popularne komunikaty NMEA
| Treść wiadomości | OPIS | Dostępność: |
|---|---|---|
| GGA | Dane GPS — pozycja, wysokość, jakość ustalenia pozycji i liczba satelitów | Wszystkie odbiorniki |
| GLL | Położenie geograficzne — szerokość i długość geograficzna wraz z czasem i statusem | Wszystkie odbiorniki |
| GNS | Dane GNSS Fix — podobne do GGA, ale obsługujące wiele konstelacji (GPS, GLONASS, Galileo...) | Wszystkie odbiorniki |
| GRS | Resztki zasięgu GNSS — resztki zasięgów używanych w rozwiązaniu nawigacyjnym | Wszystkie odbiorniki |
| GSA | GNSS DOP i aktywne satelity — typ stały (2D/3D) i używane satelity | Wszystkie odbiorniki |
| GST | Statystyki błędów pseudopomarańczowych GNSS — szacunki błędów położenia (RMS, szerokość geograficzna, długość geograficzna, wysokość) | Wszystkie odbiorniki |
| GSV | Satelity GNSS w zasięgu wzroku — liczba, wysokość, azymut i siła sygnału widocznych satelitów | Wszystkie odbiorniki |
| HDT | Kurs rzeczywisty — rzeczywisty kurs statku względem prawdziwej północy | Septentrio Mosaic-H simpleRTK3B Heading |
| INSPVAXA | Dane z Sensor Fusion — zintegrowane położenie, prędkość, postawa i ich szacowane błędy | Unicore UM981 simpleRTK3B Fusion |
| PUBX,00 | Dane o położeniu — szerokość geograficzna, długość geograficzna, wysokość i jakość ustalenia pozycji (u-blox urządzenia) | Wszystkie produkty u-blox odbiorniki |
| PUBX,04 | Pora dnia — czas UTC i dane zegarowe (u-blox urządzenia) | Wszystkie produkty u-blox odbiorniki |
| PKM | Zalecane minimalne dane GNSS — pozycja, prędkość, kurs i data | Wszystkie odbiorniki |
| RED | Prędkość obrotu — prędkość obrotu statku w stopniach na minutę | Septentrio Mosaic-H simpleRTK3B Heading |
| VTG | Kurs nad ziemią i prędkość względem ziemi — tor i prędkość w węzłach i km/h | Wszystkie odbiorniki |
| USA | Czas i data — czas UTC, dzień, miesiąc, rok i lokalna strefa czasowa | Wszystkie odbiorniki |
Struktura wiadomości NMEA
Każda wiadomość zaczyna się od $ znak, po którym następuje krótki kod identyfikujący typ zawartych w nim danych (patrz tabela w następnej sekcji).
Następnie odbiorca wypełnia wszystkie pola danych oddzielone przecinkami — szerokość geograficzna, długość geograficzna, wysokość, czas, liczbę satelitów itd. — i kończy wiadomość suma kontrolna, która jest niewielką liczbą umożliwiającą urządzeniu odbiorczemu sprawdzenie, czy dane nie zostały uszkodzone podczas transmisji.
Wiadomość kończy się podziałem wiersza, a następna wiadomość zaczyna się zaraz po nim.
Poniższy rysunek przedstawia sposób generowania komunikatu NMEA.
Generowanie sumy kontrolnej NMEA
Przykłady kodu generującego sumę kontrolną NMEA na podstawie ładunku NMEA:
def nmea_checksum(payload):
checksum = 0
for char in payload:
checksum ^= ord(char)
return f"{checksum:02X}"
# Pass only the part between $ and *
print(nmea_checksum("GNGGA,092725.00,4717.11399,N,00833.91986,E,1,08,1.01,499.6,M,48.0,M,,"))
# Returns: '4E' (or whatever the correct checksum is)
Walidacja sumy kontrolnej NMEA
Jeśli chcesz sprawdzić, czy komunikat NMEA jest prawdziwy, skorzystaj z poniższego przykładowego kodu:
def validate_nmea(sentence):
sentence = sentence.strip()
if not sentence.startswith('$') or '*' not in sentence:
return False
payload, claimed = sentence[1:].split('*', 1)
checksum = 0
for char in payload:
checksum ^= ord(char)
return f"{checksum:02X}" == claimed.strip()[:2].upper()
print(validate_nmea("$GPRMC,123519,A,4807.038,N,01131.000,E,022.4,084.4,230394,003.1,W*6A")) # True
print(validate_nmea("$GPRMC,123519,A,4807.038,N,01131.000,E,022.4,084.4,230394,003.1,W*FF")) # False
print(validate_nmea("invalid sentence")) # False
#include
#include
#include
#include
bool validate_nmea(const char *sentence) {
if (!sentence || *sentence != '$') return false;
const char *star = strchr(sentence, '*');
if (!star || strlen(star) < 3) return false;
uint8_t checksum = 0;
const char *p = sentence + 1;
while (p != star) {
checksum ^= (uint8_t)*p++;
}
uint8_t claimed;
if (sscanf(star + 1, "%2hhX", &claimed) != 1) return false;
return checksum == claimed;
}
int main() {
printf("%d\n", validate_nmea("$GPRMC,123519,A,4807.038,N,01131.000,E,022.4,084.4,230394,003.1,W*6A")); // 1
printf("%d\n", validate_nmea("$GPRMC,123519,A,4807.038,N,01131.000,E,022.4,084.4,230394,003.1,W*FF")); // 0
printf("%d\n", validate_nmea(NULL)); // 0
printf("%d\n", validate_nmea("invalid")); // 0
return 0;
}
function validateNmea(sentence) {
sentence = sentence.trim();
if (!sentence.startsWith('$') || !sentence.includes('*')) return false;
const starIdx = sentence.indexOf('*');
const payload = sentence.slice(1, starIdx);
const claimed = sentence.slice(starIdx + 1, starIdx + 3).toUpperCase();
if (claimed.length < 2 || !/^[0-9A-F]{2}$/.test(claimed)) return false;
let checksum = 0;
for (let i = 0; i < payload.length; i++) {
checksum ^= payload.charCodeAt(i);
}
return checksum.toString(16).toUpperCase().padStart(2, '0') === claimed;
}
validateNmea("$GPRMC,123519,A,4807.038,N,01131.000,E,022.4,084.4,230394,003.1,W*6A"); // true
validateNmea("$GPRMC,123519,A,4807.038,N,01131.000,E,022.4,084.4,230394,003.1,W*FF"); // false
validateNmea("invalid"); // false
#include
#include
#include
uint8_t nmea_checksum(const char *sentence) {
// Skip leading '$' if present
if (*sentence == '$') sentence++;
uint8_t checksum = 0;
while (*sentence && *sentence != '*') {
checksum ^= (uint8_t)*sentence++;
}
return checksum;
}
int main() {
const char *sentence = "$GNGGA,092725.00,4717.11399,N,00833.91986,E,1,08,1.01,499.6,M,48.0,M,,";
printf("Checksum: %02X\n", nmea_checksum(sentence));
return 0;
}
function nmeaChecksum(sentence) {
// Strip leading $ and everything from * onwards
sentence = sentence.replace(/^\$/, '').split('*')[0];
let checksum = 0;
for (let i = 0; i < sentence.length; i++) {
checksum ^= sentence.charCodeAt(i);
}
return checksum.toString(16).toUpperCase().padStart(2, '0');
}
nmeaChecksum("GNGGA,092725.00,4717.11399,N,00833.91986,E,1,08,1.01,499.6,M,48.0,M,,");
// Returns: "4E"
Kalkulator sum kontrolnych NMEA online
Suma kontrolna (szesnastkowa)
--
Suma kontrolna (dziesiętna)
--
Długość ładunku
--
Pełne zdanie
-
Zweryfikuj zdanie