Marine AIS Empfänger und Transponder geben über den NMEA0183 Ausgang die AIS Informationen im !AIVDM Datensatz aus. Diese Informationen sind 6-Bit codiert und können aus dem Datensatz extrahiert werden.
Ich verwende hier für erste Versuche zur Decodierung den Arduino Uno mit 3 Testdatensätzen.
In dieser Software Version 0.1 werden die Messages 1, 2 und 3 untersucht.
Daraus werden die folgenden Daten extrahiert.
– die Checksumme wird überprüft, die Erklärung hier.
– der AIS Kanal
– der Message Typ
– die MMSI
– der NavStatus
– SOG / Geschwindigleit über Grund
– Breite der Position in Minuten
– Länge der Position in Minuten
– Kurs
– Heading / Vorausrichtung
Die Daten werden im Konsolenfenster ausgegeben.
Die Software.
// AIS Daten Decoder // Für VDM Message 1,2 und 3 // Mit den AIS VDM Funktionen // Checksumme überprüfen, AIS Kanal, Message Typ, MMSI, NavStatus, SOG, Breite, Länge, Kurs, Heading // // Matthias Busse Version 0.1 vom 23.11.2014 veröffentlicht // von http://www.it-digin.com/blog/?p=20 String ais1a="!AIVDM,1,1,,A,133m@ogP00PD;88MD5MTDww@2D7k,0*46"; //Message 123 // von http://www.maritec.co.za/aisvdmvdodecoding1.php String ais1b="!AIVDM,1,1,,A,13u?etPv2;0n:dDPwUM1U1Cb069D,0*24"; //Message 123 // von http://www.nmea.de Datensätze vom AMEC Camino-108 String ais1c="!AIVDM,1,1,,B,139LS80w@00fIEjO55K:2oi828E=,0*7E"; String data="123456789012345678901234567890123456789012345678901234567890"; String navstatus="Reserved for future amendment of Navigational Status for HSC"; int kanal, intzahl, grad; long longzahl; float floatzahl; double longi, lati; void setup() { Serial.begin(4800); Serial.println(" AIS Decoder Version 0.1\n"); } void loop() { check_all(ais1a); Serial.println(); check_all(ais1b); Serial.println(); check_all(ais1c); Serial.println(); delay(500000); } void check_all(String s) { Serial.print("AIS Datensatz: "); Serial.println(s); if(CheckSumOK(s)) Serial.println("Checksumme OK"); else Serial.println("Checksumme Falsch"); Serial.print("Datenfeld: "); data=vdm_cut_data(s); Serial.println(data); Serial.print("Kanal: "); kanal=vdm_get_kanal(s); if(kanal == 1) Serial.println("A"); if(kanal == 2) Serial.println("B"); Serial.print("Message Type: "); Serial.println(vdm_get_mtype(data)); Serial.print("MMSI: "); Serial.println(vdm_get_mmsi(data)); Serial.print("Nav. Status: "); longzahl=vdm123_get_navstatus(data); Serial.print(longzahl); Serial.print(" das ist: "); navstatus=vdm123_get_navstatus((int)longzahl); Serial.println(navstatus); Serial.print("SOG:"); Serial.println(vdm123_get_sog(data),1); Serial.print("Longitude: "); longi=vdm123_get_longitude(data); grad=(int)longi/60; Serial.print(grad); Serial.print(" Grad "); Serial.print(longi-(grad*60),4); Serial.println(" Minuten"); Serial.print("Latitude: "); lati=vdm123_get_latitude(data); grad=(int)lati/60; Serial.print(grad); Serial.print(" Grad "); Serial.print(lati-(grad*60),4); Serial.println(" Minuten"); Serial.print("COG: "); Serial.println(vdm123_get_cog(data),1); Serial.print("Heading: "); Serial.println(vdm123_get_heading(data)); } float vdm123_get_cog(String d) { // COG Kurs über Grund aus VDM Message 1,2,3 // Matthias Busse 23.11.2014 Version 1.0 return (vdm_get_long_int(d, 116, 12) & 0x0FFF) / 10.0; } double vdm123_get_longitude(String d) { // Länge ausgeben in Minuten aus VDM Message 1,2,3 // Matthias Busse 23.11.2014 Version 1.0 return (vdm_get_long_int(d, 61, 28) & 0x0FFFFFFF) / 10000.0; } double vdm123_get_latitude(String d) { // Breite ausgeben in Minuten aus VDM Message 1,2,3 // Matthias Busse 23.11.2014 Version 1.0 return (vdm_get_long_int(d, 89, 27) & 0x07FFFFFF) / 10000.0; } String vdm123_get_navstatus(int ns) { // NavStatus in String Text umwandeln aus VDM Message 1,2,3 // Matthias Busse 23.11.2014 Version 1.0 switch (ns) { case 0: return "Under way using engine"; case 1: return "At anchor"; case 2: return "Not under command"; case 3: return "Restricted manoeuverability"; case 4: return "Constrained by her draught"; case 5: return "Moored"; case 6: return "Aground"; case 7: return "Engaged in Fishing"; case 8: return "Under way sailing"; case 9: return "Reserved for future amendment of Navigational Status for HSC"; case 10: return "Reserved for future amendment of Navigational Status for WIG"; case 11: return "Reserved for future use"; case 12: return "Reserved for future use"; case 13: return "Reserved for future use"; case 14: return "Reserved for future use"; case 15: return "Not defined"; default: return "Not defined"; } } long vdm123_get_navstatus(String d) { // NavStatus, 0-15, unsigned int, aus VDM Message 1,2,3 // Matthias Busse 23.11.2014 Version 1.0 return vdm_get_uint(d, 38, 4) & 0x000F; } float vdm123_get_sog(String d) { // SOG, 0-1022 = 0..102.2 kn, unsigned int / 10 aus VDM Message 1,2,3 // Matthias Busse 23.11.2014 Version 1.0 return (vdm_get_long_int(d, 50, 10) & 0x03FF) / 10.0; } unsigned int vdm123_get_heading(String d) { // Heading True, 0-359 Grad, unsigned int, aus VDM Message 1,2,3 // Matthias Busse 23.11.2014 Version 1.0 return vdm_get_uint(d, 128, 9) & 0x01FF; } long vdm_get_uint(String d, int start, int bits) { // unsigned int (32bit) aus dem VDM Satz rauslesen // Eingaben: String d, nur das Datenfeld aus dem VDM Datensatz // Ausgabe: unsigned integer // Matthias Busse 19.12.2012 Version 1.0 int i, stelle=0, schieben, fertig=0, durchlauf=1, modulo; long k, b=0; unsigned int uintzahl=0; // erstmal weg zählen for(i=6; i<start; i+=6) stelle++; // an welcher Stelle starten modulo = start % 6; while(fertig < (bits+modulo)) { k=d.charAt(stelle)-48; if(k>40) k-=8; schieben= bits-(durchlauf*6)+modulo; if(schieben >= 0) {b = k << schieben;} // 26: 4 zu 30, Rest 26 else {b = k >> abs(schieben);} uintzahl |= b; fertig += 6; stelle++; durchlauf++; } return uintzahl; } long vdm_get_long_int(String d, int start, int bits) { // long int (32bit)aus dem VDM Satz rauslesen // Eingaben: String d, nur das Datenfeld aus dem VDM Datensatz // Ausgabe: long integer // Matthias Busse 19.12.2012 Version 1.0 int i, stelle=0, schieben, fertig=0, durchlauf=1, modulo; long k, b=0, mmsi=0; // 32 Bit // erstmal weg zählen for(i=6; i<start; i+=6) stelle++; // an welcher Stelle starten modulo = start % 6; while(fertig < (bits+modulo)) { k=d.charAt(stelle)-48; if(k>40) k-=8; schieben= bits-(durchlauf*6)+modulo; if(schieben >= 0) {b = k << schieben;} // 26: 4 zu 30, Rest 26 else {b = k >> abs(schieben);} mmsi |= b; fertig += 6; stelle++; durchlauf++; } return mmsi; } long vdm_get_mmsi(String d) { // MMSI aus dem ganzen VDM Datensatz lesen. // Eingabe : String d, nur das Datenfeld aus dem VDM Datensatz // Ausgabe : long integer MMSI (! führende Nullen fehlen) // benötigt: vdm_cut_data(String) // Getestet für Message Typ: 1-6, 8, 11, 14, 18, 19, 21, 24 // Matthias Busse 1.2013 Version 1.0 int start=8, bits=30; // MMSI startet mit Bit 8 und ist 30 Bit lang int i, stelle=0, schieben, fertig=0, durchlauf=1, modulo; long k, b=0, mmsi=0; // 32 Bit // erstmal weg zählen for(i=6; i<start; i+=6) stelle++; // an welcher Stelle starten modulo = start % 6; while(fertig < (bits+modulo)) { k=d.charAt(stelle)-48; if(k > 40) k-=8; schieben= bits-(durchlauf*6)+modulo; if(schieben >= 0) {b = k << schieben;} // 26: 4 zu 30, Rest 26 else {b = k >> abs(schieben);} mmsi |= b; fertig += 6; stelle++; durchlauf++; } return mmsi; } int vdm_get_mtype(String d) { // VDM Message Typen aus VDM Datenfeld d auslesen // die ersten 6 Bit = Message Type // int 1-27 kommt zurück, 0 ist ein Fehler // Getestet für Message Typ: 1-6, 8, 11, 14, 18, 19, 21, 24, 27 // Matthias Busse 23.11.2014 Version 1.1 int k=d.charAt(0)-48; if(k>40) k-=8; return k; } int vdm_get_kanal(String s) { // Kanalnummer ausgeben aus komplettem VDM String s // Kanal A=1, B=2, Fehler=0 // Eingabe: String s, ist ganzer VDM String (nicht nur data) // Ausgabe: integer A=1,B=2,Fehler=0 // Matthias Busse 23.11.2014 Version 1.1 String c; int pos1; for (int i=0; i<4; i++) // 4x Komma weglesen pos1 = s.indexOf( ',', pos1+1); c = s.substring(pos1+1, pos1+2); if(c == "A") return 1; if(c == "B") return 2; return 0; } String vdm_cut_data(String s) { // Das Datenfeld aus dem VDM String s ausschneiden // Matthias Busse 23.11.2014 Version 1.0 int i, pos1=0, pos2=0; for (i=0; i<5; i++) // 5x Komma weglesen pos1 = s.indexOf( ',', pos1+1); pos2 = s.indexOf( ',', pos1+1); return s.substring(pos1+1, pos2); } int CheckSumOK(String s) { // Länge und Checksumme des NMEA Datesatzes s überprüfen // Matthias Busse Version 1.1 vom 23.11.2014 int i, k, cs1, cs2; i=s.indexOf('*'); if(i<6) return 0; // Fehler: Datensatz zu kurz cs1=getCheckSum(s); k = (int)s.charAt(i+1); // CS 1. Zeichen if(k<59) k -=48; // 0= ASCII 48 - 48 = 0 else k -=55; // A= ASCII 65 - 55 = 10 cs2 = k * 16; k = int(s.charAt(i+2)); // CS 2. Zeichen if(k<59) k -=48; // 0= ASCII 48 - 48 = 0 else k -=55; // A= ASCII 65 - 55 = 10 cs2 += k; if(cs1 != cs2) return 0; // Fehler: Checksumme falsch return 1; // alles OK } int getCheckSum(String s) { // Checksum berechnen und als int ausgeben // wird als HEX benötigt im NMEA Datensatz // Matthias Busse 23.11.2014 Version 1.1 int i, XOR, c; for (XOR = 0, i = 0; i < s.length(); i++) { c = (unsigned char)s.charAt(i); if (c == '*') break; if ((c != '!') && (c != '$')) XOR ^= c; } return XOR; }
Informationen zu NMEA0183 Daten und Testdatensätze sind unter nmea.de zu finden.
Hier können AIS Datensätze zum Testen umgerechnet werden.
von Matthias Busse
Hello Matthias!
Ich hat ein SR162 funk. Wie schreibt man koden so man austauschen beispiel zeilen zu einkommende funk 38400baud.
Hälsningar von Schweden
Johnny
Hallo Johnny,
ich schreibe das morgen mal zusammen.
Dann kannst Du das ausprobieren.
Hier das Beispiel.
Gruesse, Matthias
Pingback: AIS Daten vom Cypho-150 Empfänger live einlesen und die MMSI ausgeben | Shelvin – Elektronik ausprobiert und erläutert
Hallo Matthias,
ich komme nicht dahinter, was bei der Ausgabe der Funktionswerte die bitweise „&“ Verknüpfung von Beispielsweise „vdm123_get_heading(String d)“ noch macht.
„return vdm_get_uint(d, 128, 9) & 0x01FF;“
Wofür steht das 0x01FF? Und wie müsste es für Rate of Turn aussehen?
Gruß
Sascha