AIS VDM Datensatz Decoder mit dem Arduino Uno

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

3 Gedanken zu „AIS VDM Datensatz Decoder mit dem Arduino Uno

  1. Pingback: AIS Daten vom Cypho-150 Empfänger live einlesen und die MMSI ausgeben | Shelvin – Elektronik ausprobiert und erläutert

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.