Skip to main content

NMEA Data Integration

Receive and decode NMEA GPS and traffic data for positioning and aircraft detection.

NMEA Message Overview

Aero-Tracker devices output standard NMEA 0183 sentences based on the FLARM data port specification.

Message Types

Standard GPS/GNSS Messages

  • $GPGGA / $GNGGA - Global Positioning System Fix Data (time, position, fix quality)
  • $GNRMC - Recommended Minimum Navigation Information (position, velocity, time)
  • $GNVTG - Track Made Good and Ground Speed (course and speed information)
  • $GNGLL - Geographic Position - Latitude/Longitude (position and time)
  • $GRMZ - Altitude Information (altitude above mean sea level in feet)

FLARM-Specific Traffic Messages

  • $PFLAA - Traffic Data (relative positions of nearby aircraft)
  • $PFLAU - Operating Status and Priority Intruder (traffic summary and system status)

NMEA Examples

Here are examples of typical NMEA sentences you'll receive:

$GNGGA,123519.00,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*42
$GNRMC,123519.00,A,4807.038,N,01131.000,E,022.4,084.4,230394,003.1,W*6A
$GNVTG,084.4,T,081.3,M,022.4,N,041.5,K*52
$GNGLL,4807.038,N,01131.000,E,123519.00,A,A*68
$GRMZ,1791,f,3*1B
$PFLAU,2,1,2,1,0,,0,,,CB3415*4F
$PFLAA,0,123,-456,789,2,CB3415,180,2,45,1.2,1*7A

Data Processing

NMEA Sentence Buffering

NMEA data may arrive in fragments. Always use buffering to reassemble complete sentences:

let buffer = '';

function processNMEAData(chunk: string) {
  buffer += chunk;
  
  // Extract complete NMEA sentences ($ to checksum)
  const nmeaRegex = /\$(?:[^$*]+)\*[0-9A-Fa-f]{2}/g;
  const sentences = buffer.match(nmeaRegex) || [];
  
  // Process each complete sentence
  sentences.forEach(sentence => {
    parseNMEASentence(sentence);
  });
  
  // Keep remaining incomplete data
  const lastIndex = buffer.lastIndexOf(sentences[sentences.length - 1] || '');
  buffer = lastIndex >= 0 ? buffer.slice(lastIndex + sentences[sentences.length - 1]?.length || 0) : buffer;
}

FLARM Traffic Parsing

The $PFLAA sentence contains relative traffic positions that must be converted to absolute coordinates using your current GPS position as reference:

function parsePFLAA(sentence: string, referencePosition: {lat: number, lon: number, alt: number}) {
  const parts = sentence.split(',');
  
  // Extract relative distances in meters
  const northDistance = parseFloat(parts[2]);  // North-South distance
  const eastDistance = parseFloat(parts[3]);   // East-West distance  
  const verticalDistance = parseFloat(parts[4]); // Vertical separation
  
  // Convert to absolute position using Earth's radius
  const earthRadius = 6378137.0;
  const latOffset = (northDistance / earthRadius) * (180.0 / Math.PI);
  const lonOffset = (eastDistance / (earthRadius * Math.cos(referencePosition.lat * Math.PI / 180.0))) * (180.0 / Math.PI);
  
  // Parse additional traffic information
  const addressType = parts[5];           // Address type (0=FLARM, 1=ADS-B, etc.)
  const address = parts[6];               // Aircraft address/ID
  const track = parseInt(parts[7]) || 0;  // Track/heading in degrees
  const turnRate = parseInt(parts[8]) || 0; // Turn rate in degrees/second
  const groundSpeed = parseInt(parts[9]) || 0; // Ground speed in km/h
  const climbRate = parseInt(parts[10]) || 0;  // Climb rate in m/s
  const aircraftType = parts[11].split('*')[0]; // Aircraft type (hex)
  
  return {
    id: address,
    latitude: referencePosition.lat + latOffset,
    longitude: referencePosition.lon + lonOffset,
    altitude: referencePosition.alt + verticalDistance,
    ground_speed: groundSpeed,
    course: track,
    vertical_rate: climbRate,
    turn_rate: turnRate,
    aircraft_type: aircraftType,
    address_type: addressType
  };
}

System Status Parsing

The $PFLAU sentence provides system status and priority intruder information:

function parsePFLAU(sentence: string) {
  const parts = sentence.split(',');
  
  return {
    rx_count: parseInt(parts[1]) || 0,        // Number of received FLARM devices
    tx_status: parts[2],                      // Transmission status
    gps_status: parts[3],                     // GPS status (0=no GPS, 2=3D fix)
    power_status: parts[4],                   // Power status
    alarm_level: parseInt(parts[5]) || 0,     // Alarm level (0-3)
    relative_bearing: parseInt(parts[6]) || 0, // Relative bearing to priority target
    alarm_type: parts[7],                     // Type of alarm
    relative_vertical: parseInt(parts[8]) || 0, // Relative vertical distance
    relative_distance: parseInt(parts[9]) || 0, // Relative horizontal distance
    id: parts[10]?.split('*')[0]             // ID of priority target
  };
}