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
};
}