This commit addresses issue #23 where aircraft track changes were not propagating properly to the web frontend. The fixes include: **Server-side improvements:** - Enhanced WebSocket broadcast reliability with timeout-based queueing - Increased broadcast channel buffer size (1000 -> 2000) - Improved error handling and connection management - Added write timeouts to prevent slow clients from blocking updates - Enhanced connection cleanup and ping/pong handling - Added debug endpoint /api/debug/websocket for troubleshooting - Relaxed position validation thresholds for better track acceptance **Frontend improvements:** - Enhanced WebSocket manager with exponential backoff reconnection - Improved aircraft position update detection and logging - Fixed position update logic to always propagate changes to map - Better coordinate validation and error reporting - Enhanced debugging with detailed console logging - Fixed track rotation update thresholds and logic - Improved marker lifecycle management and cleanup - Better handling of edge cases in aircraft state transitions **Key bug fixes:** - Removed overly aggressive position change detection that blocked updates - Fixed track rotation sensitivity (5° -> 10° threshold) - Enhanced coordinate validation to handle null/undefined values - Improved WebSocket message ordering and processing - Fixed marker position updates to always propagate to Leaflet These changes ensure reliable real-time aircraft tracking with proper position, heading, and altitude updates across multiple data sources. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1195 lines
42 KiB
Go
1195 lines
42 KiB
Go
// Package modes provides Mode S and ADS-B message decoding capabilities.
|
|
//
|
|
// Mode S is a secondary surveillance radar system that enables aircraft to transmit
|
|
// detailed information including position, altitude, velocity, and identification.
|
|
// ADS-B (Automatic Dependent Surveillance-Broadcast) is a modernization of Mode S
|
|
// that provides more precise and frequent position reports.
|
|
//
|
|
// This package implements:
|
|
// - Complete Mode S message decoding for all downlink formats
|
|
// - ADS-B extended squitter message parsing (DF17/18)
|
|
// - CPR (Compact Position Reporting) position decoding algorithm
|
|
// - Aircraft identification, category, and status decoding
|
|
// - Velocity and heading calculation from velocity messages
|
|
// - Navigation accuracy and integrity decoding
|
|
//
|
|
// Key Features:
|
|
// - CPR Global Position Decoding: Resolves ambiguous encoded positions using
|
|
// even/odd message pairs and trigonometric calculations
|
|
// - Multi-format Support: Handles surveillance replies, extended squitter,
|
|
// and various ADS-B message types
|
|
// - Real-time Processing: Maintains state for CPR decoding across messages
|
|
// - Comprehensive Data Extraction: Extracts all available aircraft parameters
|
|
//
|
|
// CPR Algorithm Implementation:
|
|
// The Compact Position Reporting format encodes latitude and longitude using
|
|
// two alternating formats (even/odd) that create overlapping grids. The decoder
|
|
// uses both messages to resolve the ambiguity and calculate precise positions.
|
|
//
|
|
// This implementation follows authoritative sources:
|
|
// - RTCA DO-260B / EUROCAE ED-102A: ADS-B Minimum Operational Performance Standards
|
|
// - ICAO Annex 10, Volume IV: Surveillance and Collision Avoidance Systems
|
|
// - "Decoding ADS-B position" by Edward Lester (http://www.lll.lu/~edward/edward/adsb/DecodingADSBposition.html)
|
|
// - PyModeS library reference implementation (https://github.com/junzis/pyModeS)
|
|
package modes
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
"sync"
|
|
)
|
|
|
|
// crcTable for Mode S CRC-24 validation
|
|
var crcTable [256]uint32
|
|
|
|
func init() {
|
|
// Initialize CRC table for Mode S CRC-24 (polynomial 0x1FFF409)
|
|
for i := 0; i < 256; i++ {
|
|
crc := uint32(i) << 16
|
|
for j := 0; j < 8; j++ {
|
|
if crc&0x800000 != 0 {
|
|
crc = (crc << 1) ^ 0x1FFF409
|
|
} else {
|
|
crc = crc << 1
|
|
}
|
|
}
|
|
crcTable[i] = crc & 0xFFFFFF
|
|
}
|
|
}
|
|
|
|
// validateModeSCRC validates the 24-bit CRC of a Mode S message
|
|
func validateModeSCRC(data []byte) bool {
|
|
if len(data) < 4 {
|
|
return false
|
|
}
|
|
|
|
// Calculate CRC for all bytes except the last 3 (which contain the CRC)
|
|
crc := uint32(0)
|
|
for i := 0; i < len(data)-3; i++ {
|
|
crc = ((crc << 8) ^ crcTable[((crc>>16)^uint32(data[i]))&0xFF]) & 0xFFFFFF
|
|
}
|
|
|
|
// Extract transmitted CRC from last 3 bytes
|
|
transmittedCRC := uint32(data[len(data)-3])<<16 | uint32(data[len(data)-2])<<8 | uint32(data[len(data)-1])
|
|
|
|
return crc == transmittedCRC
|
|
}
|
|
|
|
// Mode S Downlink Format (DF) constants.
|
|
// The DF field (first 5 bits) determines the message type and structure.
|
|
const (
|
|
DF0 = 0 // Short air-air surveillance (ACAS)
|
|
DF4 = 4 // Surveillance altitude reply (interrogation response)
|
|
DF5 = 5 // Surveillance identity reply (squawk code response)
|
|
DF11 = 11 // All-call reply (capability and ICAO address)
|
|
DF16 = 16 // Long air-air surveillance (ACAS with altitude)
|
|
DF17 = 17 // Extended squitter (ADS-B from transponder)
|
|
DF18 = 18 // Extended squitter/non-transponder (ADS-B from other sources)
|
|
DF19 = 19 // Military extended squitter
|
|
DF20 = 20 // Comm-B altitude reply (BDS register data)
|
|
DF21 = 21 // Comm-B identity reply (BDS register data)
|
|
DF24 = 24 // Comm-D (ELM - Enhanced Length Message)
|
|
)
|
|
|
|
// ADS-B Type Code (TC) constants for DF17/18 extended squitter messages.
|
|
// The type code (bits 32-36) determines the content and format of ADS-B messages.
|
|
const (
|
|
TC_IDENT_CATEGORY = 1 // Aircraft identification and category (callsign)
|
|
TC_SURFACE_POS = 5 // Surface position (airport ground movement)
|
|
TC_AIRBORNE_POS_9 = 9 // Airborne position (barometric altitude)
|
|
TC_AIRBORNE_POS_20 = 20 // Airborne position (GNSS height above ellipsoid)
|
|
TC_AIRBORNE_VEL = 19 // Airborne velocity (ground speed and track)
|
|
TC_AIRBORNE_POS_GPS = 22 // Airborne position (GNSS altitude)
|
|
TC_RESERVED = 23 // Reserved for future use
|
|
TC_SURFACE_SYSTEM = 24 // Surface system status
|
|
TC_OPERATIONAL = 31 // Aircraft operational status (capabilities)
|
|
)
|
|
|
|
// Aircraft represents a complete set of decoded aircraft data from Mode S/ADS-B messages.
|
|
//
|
|
// This structure contains all possible information that can be extracted from
|
|
// various Mode S and ADS-B message types, including position, velocity, status,
|
|
// and navigation data. Not all fields will be populated for every aircraft,
|
|
// depending on the messages received and aircraft capabilities.
|
|
type Aircraft struct {
|
|
// Core Identification
|
|
ICAO24 uint32 // 24-bit ICAO aircraft address (unique identifier)
|
|
Callsign string // 8-character flight callsign (from identification messages)
|
|
|
|
// Position and Navigation
|
|
Latitude float64 // Position latitude in decimal degrees
|
|
Longitude float64 // Position longitude in decimal degrees
|
|
Altitude int // Altitude in feet (barometric or geometric)
|
|
BaroAltitude int // Barometric altitude in feet (QNH corrected)
|
|
GeomAltitude int // Geometric altitude in feet (GNSS height)
|
|
|
|
// Motion and Dynamics
|
|
VerticalRate int // Vertical rate in feet per minute (climb/descent)
|
|
GroundSpeed int // Ground speed in knots (integer)
|
|
Track int // Track angle in degrees (0-359, integer)
|
|
Heading int // Aircraft heading in degrees (magnetic, integer)
|
|
|
|
// Aircraft Information
|
|
Category string // Aircraft category (size, type, performance)
|
|
Squawk string // 4-digit transponder squawk code (octal)
|
|
|
|
// Status and Alerts
|
|
Emergency string // Emergency/priority status description
|
|
OnGround bool // Aircraft is on ground (surface movement)
|
|
Alert bool // Alert flag (ATC attention required)
|
|
SPI bool // Special Position Identification (pilot activated)
|
|
|
|
// Data Quality Indicators
|
|
NACp uint8 // Navigation Accuracy Category - Position (0-11)
|
|
NACv uint8 // Navigation Accuracy Category - Velocity (0-4)
|
|
SIL uint8 // Surveillance Integrity Level (0-3)
|
|
|
|
// Transponder Information
|
|
TransponderCapability string // Transponder capability level (from DF11 messages)
|
|
TransponderLevel uint8 // Transponder level (0-7 from capability field)
|
|
|
|
// Combined Data Quality Assessment
|
|
SignalQuality string // Combined assessment of position/velocity accuracy and integrity
|
|
|
|
// Autopilot/Flight Management
|
|
SelectedAltitude int // MCP/FCU selected altitude in feet
|
|
SelectedHeading float64 // MCP/FCU selected heading in degrees
|
|
BaroSetting float64 // Barometric pressure setting (QNH) in millibars
|
|
}
|
|
|
|
// Decoder handles Mode S and ADS-B message decoding with CPR position resolution.
|
|
//
|
|
// The decoder maintains state for CPR (Compact Position Reporting) decoding,
|
|
// which requires pairs of even/odd messages to resolve position ambiguity.
|
|
// Each aircraft (identified by ICAO24) has separate CPR state tracking.
|
|
//
|
|
// CPR Position Decoding:
|
|
// Aircraft positions are encoded using two alternating formats that create
|
|
// overlapping latitude/longitude grids. The decoder stores both even and odd
|
|
// encoded positions and uses trigonometric calculations to resolve the
|
|
// actual aircraft position when both are available.
|
|
type Decoder struct {
|
|
// CPR (Compact Position Reporting) state tracking per aircraft
|
|
cprEvenLat map[uint32]float64 // Even message latitude encoding (ICAO24 -> normalized lat)
|
|
cprEvenLon map[uint32]float64 // Even message longitude encoding (ICAO24 -> normalized lon)
|
|
cprOddLat map[uint32]float64 // Odd message latitude encoding (ICAO24 -> normalized lat)
|
|
cprOddLon map[uint32]float64 // Odd message longitude encoding (ICAO24 -> normalized lon)
|
|
cprEvenTime map[uint32]int64 // Timestamp of even message (for freshness comparison)
|
|
cprOddTime map[uint32]int64 // Timestamp of odd message (for freshness comparison)
|
|
|
|
// Reference position for CPR zone ambiguity resolution (receiver location)
|
|
refLatitude float64 // Receiver latitude in decimal degrees
|
|
refLongitude float64 // Receiver longitude in decimal degrees
|
|
|
|
// Mutex to protect concurrent access to CPR maps
|
|
mu sync.RWMutex
|
|
}
|
|
|
|
// NewDecoder creates a new Mode S/ADS-B decoder with initialized CPR tracking.
|
|
//
|
|
// The reference position (typically the receiver location) is used to resolve
|
|
// CPR zone ambiguity during position decoding. Without a proper reference,
|
|
// aircraft can appear many degrees away from their actual position.
|
|
//
|
|
// Parameters:
|
|
// - refLat: Reference latitude in decimal degrees (receiver location)
|
|
// - refLon: Reference longitude in decimal degrees (receiver location)
|
|
//
|
|
// Returns a configured decoder ready for message processing.
|
|
func NewDecoder(refLat, refLon float64) *Decoder {
|
|
return &Decoder{
|
|
cprEvenLat: make(map[uint32]float64),
|
|
cprEvenLon: make(map[uint32]float64),
|
|
cprOddLat: make(map[uint32]float64),
|
|
cprOddLon: make(map[uint32]float64),
|
|
cprEvenTime: make(map[uint32]int64),
|
|
cprOddTime: make(map[uint32]int64),
|
|
refLatitude: refLat,
|
|
refLongitude: refLon,
|
|
}
|
|
}
|
|
|
|
// Decode processes a Mode S message and extracts all available aircraft information.
|
|
//
|
|
// This is the main entry point for message decoding. The method:
|
|
// 1. Validates message length and extracts the Downlink Format (DF)
|
|
// 2. Extracts the ICAO24 aircraft address
|
|
// 3. Routes to appropriate decoder based on message type
|
|
// 4. Returns populated Aircraft struct with available data
|
|
//
|
|
// Different message types provide different information:
|
|
// - DF4/20: Altitude only
|
|
// - DF5/21: Squawk code only
|
|
// - DF17/18: Complete ADS-B data (position, velocity, identification, etc.)
|
|
//
|
|
// Parameters:
|
|
// - data: Raw Mode S message bytes (7 or 14 bytes depending on type)
|
|
//
|
|
// Returns decoded Aircraft struct or error for invalid/incomplete messages.
|
|
func (d *Decoder) Decode(data []byte) (*Aircraft, error) {
|
|
if len(data) < 7 {
|
|
return nil, fmt.Errorf("message too short: %d bytes", len(data))
|
|
}
|
|
|
|
// Validate CRC to reject corrupted messages that create ghost targets
|
|
if !validateModeSCRC(data) {
|
|
return nil, fmt.Errorf("invalid CRC - corrupted message")
|
|
}
|
|
|
|
df := (data[0] >> 3) & 0x1F
|
|
icao := d.extractICAO(data, df)
|
|
|
|
aircraft := &Aircraft{
|
|
ICAO24: icao,
|
|
}
|
|
|
|
switch df {
|
|
case DF0:
|
|
// Short Air-Air Surveillance (ACAS)
|
|
aircraft.Altitude = d.decodeAltitude(data)
|
|
case DF4, DF20:
|
|
aircraft.Altitude = d.decodeAltitude(data)
|
|
case DF5, DF21:
|
|
aircraft.Squawk = d.decodeSquawk(data)
|
|
case DF11:
|
|
// All-Call Reply - extract capability and interrogator identifier
|
|
d.decodeAllCallReply(data, aircraft)
|
|
case DF16:
|
|
// Long Air-Air Surveillance (ACAS with altitude)
|
|
aircraft.Altitude = d.decodeAltitude(data)
|
|
case DF17, DF18:
|
|
return d.decodeExtendedSquitter(data, aircraft)
|
|
case DF19:
|
|
// Military Extended Squitter - similar to DF17/18 but with military codes
|
|
return d.decodeMilitaryExtendedSquitter(data, aircraft)
|
|
case DF24:
|
|
// Comm-D Enhanced Length Message - variable length data
|
|
d.decodeCommD(data, aircraft)
|
|
}
|
|
|
|
// Always try to calculate signal quality at the end of decoding
|
|
d.calculateSignalQuality(aircraft)
|
|
|
|
return aircraft, nil
|
|
}
|
|
|
|
// extractICAO extracts the ICAO 24-bit aircraft address from Mode S messages.
|
|
//
|
|
// For most downlink formats, the ICAO address is located in bytes 1-3 of the
|
|
// message. Some formats may have different layouts, but this implementation
|
|
// uses the standard position for all supported formats.
|
|
//
|
|
// Parameters:
|
|
// - data: Mode S message bytes
|
|
// - df: Downlink format (currently unused, but available for format-specific handling)
|
|
//
|
|
// Returns the 24-bit ICAO address as a uint32.
|
|
func (d *Decoder) extractICAO(data []byte, df uint8) uint32 {
|
|
// For most formats, ICAO is in bytes 1-3
|
|
return uint32(data[1])<<16 | uint32(data[2])<<8 | uint32(data[3])
|
|
}
|
|
|
|
// decodeExtendedSquitter processes ADS-B extended squitter messages (DF17/18).
|
|
//
|
|
// Extended squitter messages contain the richest aircraft data, including:
|
|
// - Aircraft identification and category (TC 1-4)
|
|
// - Surface position and movement (TC 5-8)
|
|
// - Airborne position with various altitude sources (TC 9-18, 20-22)
|
|
// - Velocity and heading information (TC 19)
|
|
// - Aircraft status and emergency codes (TC 28)
|
|
// - Target state and autopilot settings (TC 29)
|
|
// - Operational status and navigation accuracy (TC 31)
|
|
//
|
|
// The method routes messages to specific decoders based on the Type Code (TC)
|
|
// field in bits 32-36 of the message.
|
|
//
|
|
// Parameters:
|
|
// - data: 14-byte extended squitter message
|
|
// - aircraft: Aircraft struct to populate with decoded data
|
|
//
|
|
// Returns the updated Aircraft struct or an error for malformed messages.
|
|
func (d *Decoder) decodeExtendedSquitter(data []byte, aircraft *Aircraft) (*Aircraft, error) {
|
|
if len(data) < 14 {
|
|
return nil, fmt.Errorf("extended squitter too short: %d bytes", len(data))
|
|
}
|
|
|
|
tc := (data[4] >> 3) & 0x1F
|
|
|
|
switch {
|
|
case tc >= 1 && tc <= 4:
|
|
// Aircraft identification
|
|
d.decodeIdentification(data, aircraft)
|
|
case tc >= 5 && tc <= 8:
|
|
// Surface position
|
|
d.decodeSurfacePosition(data, aircraft)
|
|
case tc >= 9 && tc <= 18:
|
|
// Airborne position
|
|
d.decodeAirbornePosition(data, aircraft)
|
|
case tc == 19:
|
|
// Airborne velocity
|
|
d.decodeVelocity(data, aircraft)
|
|
case tc >= 20 && tc <= 22:
|
|
// Airborne position with GNSS
|
|
d.decodeAirbornePosition(data, aircraft)
|
|
case tc == 28:
|
|
// Aircraft status
|
|
d.decodeStatus(data, aircraft)
|
|
case tc == 29:
|
|
// Target state and status
|
|
d.decodeTargetState(data, aircraft)
|
|
case tc == 31:
|
|
// Operational status
|
|
d.decodeOperationalStatus(data, aircraft)
|
|
}
|
|
|
|
// Set baseline signal quality for ADS-B extended squitter
|
|
aircraft.SignalQuality = "Good" // ADS-B extended squitter is high quality by default
|
|
|
|
// Refine quality based on NACp/NACv/SIL if available
|
|
d.calculateSignalQuality(aircraft)
|
|
|
|
return aircraft, nil
|
|
}
|
|
|
|
// decodeIdentification extracts aircraft callsign and category from identification messages.
|
|
//
|
|
// Aircraft identification messages (TC 1-4) contain:
|
|
// - 8-character callsign encoded in 6-bit characters
|
|
// - Aircraft category indicating size, performance, and type
|
|
//
|
|
// Callsign Encoding:
|
|
// Each character is encoded in 6 bits using a custom character set:
|
|
// - Characters: "#ABCDEFGHIJKLMNOPQRSTUVWXYZ##### ###############0123456789######"
|
|
// - Index 0-63 maps to the character at that position
|
|
// - '#' represents space or invalid characters
|
|
//
|
|
// Parameters:
|
|
// - data: Extended squitter message containing identification data
|
|
// - aircraft: Aircraft struct to update with callsign and category
|
|
func (d *Decoder) decodeIdentification(data []byte, aircraft *Aircraft) {
|
|
tc := (data[4] >> 3) & 0x1F
|
|
|
|
// Category
|
|
aircraft.Category = d.getAircraftCategory(tc, data[4]&0x07)
|
|
|
|
// Callsign - 8 characters encoded in 6 bits each
|
|
chars := "#ABCDEFGHIJKLMNOPQRSTUVWXYZ##### ###############0123456789######"
|
|
callsign := ""
|
|
|
|
// Extract 48 bits starting from bit 40
|
|
for i := 0; i < 8; i++ {
|
|
bitOffset := 40 + i*6
|
|
byteOffset := bitOffset / 8
|
|
bitShift := bitOffset % 8
|
|
|
|
var charCode uint8
|
|
if bitShift <= 2 {
|
|
charCode = (data[byteOffset] >> (2 - bitShift)) & 0x3F
|
|
} else {
|
|
charCode = ((data[byteOffset] << (bitShift - 2)) & 0x3F) |
|
|
(data[byteOffset+1] >> (10 - bitShift))
|
|
}
|
|
|
|
if charCode < 64 {
|
|
callsign += string(chars[charCode])
|
|
}
|
|
}
|
|
|
|
aircraft.Callsign = callsign
|
|
}
|
|
|
|
// decodeAirbornePosition extracts aircraft position from CPR-encoded position messages.
|
|
//
|
|
// Airborne position messages (TC 9-18, 20-22) contain:
|
|
// - Altitude information (barometric or geometric)
|
|
// - CPR-encoded latitude and longitude
|
|
// - Even/odd flag for CPR decoding
|
|
//
|
|
// CPR (Compact Position Reporting) Process:
|
|
// 1. Extract the even/odd flag and CPR lat/lon values
|
|
// 2. Normalize CPR values to 0-1 range (divide by 2^17)
|
|
// 3. Store values for this aircraft's ICAO address
|
|
// 4. Attempt position decoding if both even and odd messages are available
|
|
//
|
|
// The actual position calculation requires both even and odd messages to
|
|
// resolve the ambiguity inherent in the compressed encoding format.
|
|
//
|
|
// Parameters:
|
|
// - data: Extended squitter message containing position data
|
|
// - aircraft: Aircraft struct to update with position and altitude
|
|
func (d *Decoder) decodeAirbornePosition(data []byte, aircraft *Aircraft) {
|
|
tc := (data[4] >> 3) & 0x1F
|
|
|
|
// Altitude
|
|
altBits := (uint16(data[5])<<4 | uint16(data[6])>>4) & 0x0FFF
|
|
aircraft.Altitude = d.decodeAltitudeBits(altBits, tc)
|
|
|
|
// CPR latitude/longitude
|
|
cprLat := uint32(data[6]&0x03)<<15 | uint32(data[7])<<7 | uint32(data[8])>>1
|
|
cprLon := uint32(data[8]&0x01)<<16 | uint32(data[9])<<8 | uint32(data[10])
|
|
oddFlag := (data[6] >> 2) & 0x01
|
|
|
|
// Store CPR values for later decoding (protected by mutex)
|
|
d.mu.Lock()
|
|
if oddFlag == 1 {
|
|
d.cprOddLat[aircraft.ICAO24] = float64(cprLat) / 131072.0
|
|
d.cprOddLon[aircraft.ICAO24] = float64(cprLon) / 131072.0
|
|
} else {
|
|
d.cprEvenLat[aircraft.ICAO24] = float64(cprLat) / 131072.0
|
|
d.cprEvenLon[aircraft.ICAO24] = float64(cprLon) / 131072.0
|
|
}
|
|
d.mu.Unlock()
|
|
|
|
// Extract NACp (Navigation Accuracy Category for Position) from position messages
|
|
// NACp is embedded in airborne position messages in bits 50-53 (data[6] bits 1-4)
|
|
if tc >= 9 && tc <= 18 {
|
|
// For airborne position messages TC 9-18, NACp is encoded in the message
|
|
aircraft.NACp = uint8(tc - 8) // TC 9->NACp 1, TC 10->NACp 2, etc.
|
|
// Note: This is a simplified mapping. Real NACp extraction is more complex
|
|
// but this provides useful position accuracy indication
|
|
}
|
|
|
|
// Try to decode position if we have both even and odd messages
|
|
d.decodeCPRPosition(aircraft)
|
|
|
|
// Calculate signal quality whenever we have position data
|
|
d.calculateSignalQuality(aircraft)
|
|
}
|
|
|
|
// decodeCPRPosition performs CPR (Compact Position Reporting) global position decoding.
|
|
//
|
|
// Implementation follows the authoritative CPR algorithm specification from:
|
|
// - "Decoding ADS-B position" by Edward Lester: http://www.lll.lu/~edward/edward/adsb/DecodingADSBposition.html
|
|
// - RTCA DO-260B / EUROCAE ED-102A: Minimum Operational Performance Standards for ADS-B
|
|
// - ICAO Annex 10, Volume IV: Surveillance and Collision Avoidance Systems
|
|
//
|
|
// CRITICAL: The CPR algorithm has zone ambiguity that requires either:
|
|
// 1. A reference position (receiver location) to resolve zones correctly, OR
|
|
// 2. Message timestamp comparison to choose the most recent valid position
|
|
//
|
|
// Without proper zone resolution, aircraft can appear 6+ degrees away from actual position.
|
|
// This implementation uses global decoding with receiver reference position for zone selection.
|
|
//
|
|
// Algorithm Overview:
|
|
// CPR encodes positions using two alternating formats (even/odd) that create overlapping
|
|
// latitude/longitude grids. Global decoding uses both message types to resolve position
|
|
// ambiguity through trigonometric calculations and zone arithmetic.
|
|
//
|
|
// Parameters:
|
|
// - aircraft: Aircraft struct to update with decoded position
|
|
func (d *Decoder) decodeCPRPosition(aircraft *Aircraft) {
|
|
// Read CPR values with read lock
|
|
d.mu.RLock()
|
|
evenLat, evenExists := d.cprEvenLat[aircraft.ICAO24]
|
|
oddLat, oddExists := d.cprOddLat[aircraft.ICAO24]
|
|
|
|
if !evenExists || !oddExists {
|
|
d.mu.RUnlock()
|
|
return
|
|
}
|
|
|
|
evenLon := d.cprEvenLon[aircraft.ICAO24]
|
|
oddLon := d.cprOddLon[aircraft.ICAO24]
|
|
d.mu.RUnlock()
|
|
|
|
// CPR input values ready for decoding
|
|
|
|
// CPR decoding algorithm
|
|
dLat := 360.0 / 60.0
|
|
j := math.Floor(evenLat*59 - oddLat*60 + 0.5)
|
|
|
|
latEven := dLat * (math.Mod(j, 60) + evenLat)
|
|
latOdd := dLat * (math.Mod(j, 59) + oddLat)
|
|
|
|
if latEven >= 270 {
|
|
latEven -= 360
|
|
}
|
|
if latOdd >= 270 {
|
|
latOdd -= 360
|
|
}
|
|
|
|
// Additional range correction to ensure valid latitude bounds (-90° to +90°)
|
|
if latEven > 90 {
|
|
latEven = 180 - latEven
|
|
} else if latEven < -90 {
|
|
latEven = -180 - latEven
|
|
}
|
|
|
|
if latOdd > 90 {
|
|
latOdd = 180 - latOdd
|
|
} else if latOdd < -90 {
|
|
latOdd = -180 - latOdd
|
|
}
|
|
|
|
// Validate final latitude values are within acceptable range
|
|
if math.Abs(latOdd) > 90 || math.Abs(latEven) > 90 {
|
|
// Invalid CPR decoding - skip position update
|
|
return
|
|
}
|
|
|
|
// Zone ambiguity resolution using receiver reference position
|
|
// CPR global decoding produces two possible position solutions (even/odd).
|
|
// We select the solution closest to the known receiver position to resolve
|
|
// the zone ambiguity. This prevents aircraft from appearing in wrong geographic zones.
|
|
distToEven := math.Abs(latEven - d.refLatitude)
|
|
distToOdd := math.Abs(latOdd - d.refLatitude)
|
|
|
|
// Choose the latitude solution that's closer to the receiver position
|
|
// CRITICAL: This choice must be consistent for both latitude and longitude calculations
|
|
var selectedLat float64
|
|
var useOddForLongitude bool
|
|
if distToOdd < distToEven {
|
|
selectedLat = latOdd
|
|
useOddForLongitude = true
|
|
} else {
|
|
selectedLat = latEven
|
|
useOddForLongitude = false
|
|
}
|
|
aircraft.Latitude = selectedLat
|
|
|
|
// Longitude calculation using correct CPR global decoding algorithm
|
|
// Reference: http://www.lll.lu/~edward/edward/adsb/DecodingADSBposition.html
|
|
nl := d.nlFunction(selectedLat)
|
|
|
|
// Calculate longitude index M using the standard CPR formula:
|
|
// M = Int((((Lon(0) * (nl(T) - 1)) - (Lon(1) * nl(T))) / 131072) + 0.5)
|
|
// Note: Our normalized values are already divided by 131072, so we omit that division
|
|
m := math.Floor(evenLon*(nl-1) - oddLon*nl + 0.5)
|
|
|
|
// Calculate ni correctly based on frame type (CRITICAL FIX):
|
|
// From specification: ni = max(1, NL(i) - i) where i=0 for even, i=1 for odd
|
|
// For even frame (i=0): ni = max(1, NL - 0) = max(1, NL)
|
|
// For odd frame (i=1): ni = max(1, NL - 1)
|
|
//
|
|
// Previous bug: Always used NL-1, causing systematic eastward bias
|
|
var ni float64
|
|
if useOddForLongitude {
|
|
ni = math.Max(1, nl-1) // Odd frame: NL - 1
|
|
} else {
|
|
ni = math.Max(1, nl) // Even frame: NL - 0 (full NL zones)
|
|
}
|
|
|
|
// Longitude zone width in degrees
|
|
dLon := 360.0 / ni
|
|
|
|
// Calculate global longitude using frame-consistent encoding:
|
|
// Lon = dlon(T) * (modulo(M, ni) + Lon(T) / 131072)
|
|
var lon float64
|
|
if useOddForLongitude {
|
|
lon = dLon * (math.Mod(m, ni) + oddLon)
|
|
} else {
|
|
lon = dLon * (math.Mod(m, ni) + evenLon)
|
|
}
|
|
|
|
if lon >= 180 {
|
|
lon -= 360
|
|
} else if lon <= -180 {
|
|
lon += 360
|
|
}
|
|
|
|
// Validate longitude is within acceptable range
|
|
if math.Abs(lon) > 180 {
|
|
// Invalid longitude - skip position update
|
|
return
|
|
}
|
|
|
|
aircraft.Longitude = lon
|
|
|
|
// CPR decoding completed successfully
|
|
}
|
|
|
|
// nlFunction calculates the number of longitude zones (NL) for a given latitude.
|
|
//
|
|
// This function implements the NL(lat) calculation defined in the CPR specification.
|
|
// The number of longitude zones decreases as latitude approaches the poles due to
|
|
// the convergence of meridians.
|
|
//
|
|
// Mathematical Background:
|
|
// - At the equator: 60 longitude zones (6° each)
|
|
// - At higher latitudes: fewer zones as meridians converge
|
|
// - At poles (±87°): only 2 zones (180° each)
|
|
//
|
|
// Formula: NL(lat) = floor(2π / arccos(1 - (1-cos(π/(2*NZ))) / cos²(lat)))
|
|
// Where NZ = 15 (number of latitude zones)
|
|
//
|
|
// Parameters:
|
|
// - lat: Latitude in decimal degrees
|
|
//
|
|
// Returns the number of longitude zones for this latitude.
|
|
func (d *Decoder) nlFunction(lat float64) float64 {
|
|
if math.Abs(lat) >= 87 {
|
|
return 2
|
|
}
|
|
|
|
nz := 15.0
|
|
a := 1 - math.Cos(math.Pi/(2*nz))
|
|
b := math.Pow(math.Cos(math.Pi/180.0*math.Abs(lat)), 2)
|
|
nl := 2 * math.Pi / math.Acos(1-a/b)
|
|
|
|
return math.Floor(nl)
|
|
}
|
|
|
|
// decodeVelocity extracts ground speed, track, and vertical rate from velocity messages.
|
|
//
|
|
// Velocity messages (TC 19) contain:
|
|
// - Ground speed components (East-West and North-South)
|
|
// - Vertical rate (climb/descent rate)
|
|
// - Intent change flag and other status bits
|
|
//
|
|
// Ground Speed Calculation:
|
|
// - East-West and North-South velocity components are encoded separately
|
|
// - Each component has a direction bit and magnitude
|
|
// - Ground speed = sqrt(EW² + NS²)
|
|
// - Track angle = atan2(EW, NS) converted to degrees
|
|
//
|
|
// Vertical Rate:
|
|
// - Encoded in 64 ft/min increments with sign bit
|
|
// - Range: approximately ±32,000 ft/min
|
|
//
|
|
// Parameters:
|
|
// - data: Extended squitter message containing velocity data
|
|
// - aircraft: Aircraft struct to update with velocity information
|
|
func (d *Decoder) decodeVelocity(data []byte, aircraft *Aircraft) {
|
|
subtype := (data[4]) & 0x07
|
|
|
|
if subtype == 1 || subtype == 2 {
|
|
// Ground speed
|
|
ewRaw := uint16(data[5]&0x03)<<8 | uint16(data[6])
|
|
nsRaw := uint16(data[7])<<3 | uint16(data[8])>>5
|
|
|
|
ewVel := float64(ewRaw - 1)
|
|
nsVel := float64(nsRaw - 1)
|
|
|
|
if data[5]&0x04 != 0 {
|
|
ewVel = -ewVel
|
|
}
|
|
if data[7]&0x80 != 0 {
|
|
nsVel = -nsVel
|
|
}
|
|
|
|
// Calculate ground speed in knots (rounded to integer)
|
|
speedKnots := math.Sqrt(ewVel*ewVel + nsVel*nsVel)
|
|
|
|
aircraft.GroundSpeed = int(math.Round(speedKnots))
|
|
|
|
// Calculate track in degrees (0-359)
|
|
trackDeg := math.Atan2(ewVel, nsVel) * 180 / math.Pi
|
|
if trackDeg < 0 {
|
|
trackDeg += 360
|
|
}
|
|
aircraft.Track = int(math.Round(trackDeg))
|
|
}
|
|
|
|
// Vertical rate
|
|
vrSign := (data[8] >> 3) & 0x01
|
|
vrBits := uint16(data[8]&0x07)<<6 | uint16(data[9])>>2
|
|
if vrBits != 0 {
|
|
aircraft.VerticalRate = int(vrBits-1) * 64
|
|
if vrSign != 0 {
|
|
aircraft.VerticalRate = -aircraft.VerticalRate
|
|
}
|
|
}
|
|
}
|
|
|
|
// decodeAltitude extracts altitude from Mode S surveillance altitude replies.
|
|
//
|
|
// Mode S altitude replies (DF4, DF20) contain a 13-bit altitude code that
|
|
// must be converted from the transmitted encoding to actual altitude in feet.
|
|
//
|
|
// Parameters:
|
|
// - data: Mode S altitude reply message
|
|
//
|
|
// Returns altitude in feet above sea level.
|
|
func (d *Decoder) decodeAltitude(data []byte) int {
|
|
altCode := uint16(data[2])<<8 | uint16(data[3])
|
|
return d.decodeAltitudeBits(altCode>>3, 0)
|
|
}
|
|
|
|
// decodeAltitudeBits converts encoded altitude bits to altitude in feet.
|
|
//
|
|
// Altitude Encoding:
|
|
// - Uses modified Gray code for error resilience
|
|
// - 12-bit altitude code with 25-foot increments
|
|
// - Offset of -1000 feet (code 0 = -1000 ft)
|
|
// - Gray code conversion prevents single-bit errors from causing large altitude jumps
|
|
//
|
|
// Different altitude sources:
|
|
// - Standard: Barometric altitude (QNH corrected)
|
|
// - GNSS: Geometric altitude (height above WGS84 ellipsoid)
|
|
//
|
|
// Parameters:
|
|
// - altCode: 12-bit encoded altitude value
|
|
// - tc: Type code (determines altitude source interpretation)
|
|
//
|
|
// Returns altitude in feet, or 0 for invalid altitude codes.
|
|
func (d *Decoder) decodeAltitudeBits(altCode uint16, tc uint8) int {
|
|
if altCode == 0 {
|
|
return 0
|
|
}
|
|
|
|
// Standard altitude encoding with 25 ft increments
|
|
// Check Q-bit (bit 4) for encoding type
|
|
qBit := (altCode >> 4) & 1
|
|
|
|
if qBit == 1 {
|
|
// Standard altitude with Q-bit set
|
|
// Remove Q-bit and reassemble 11-bit altitude code
|
|
n := ((altCode & 0x1F80) >> 2) | ((altCode & 0x0020) >> 1) | (altCode & 0x000F)
|
|
alt := int(n)*25 - 1000
|
|
|
|
// Validate altitude range
|
|
if alt < -1000 || alt > 60000 {
|
|
return 0
|
|
}
|
|
return alt
|
|
}
|
|
|
|
// Gray code altitude (100 ft increments) - legacy encoding
|
|
// Convert from Gray code to binary
|
|
n := altCode
|
|
n ^= n >> 8
|
|
n ^= n >> 4
|
|
n ^= n >> 2
|
|
n ^= n >> 1
|
|
|
|
// Convert to altitude in feet
|
|
alt := int(n&0x7FF) * 100
|
|
if alt < 0 || alt > 60000 {
|
|
return 0
|
|
}
|
|
return alt
|
|
}
|
|
|
|
// decodeSquawk extracts the 4-digit squawk (transponder) code from identity replies.
|
|
//
|
|
// Squawk codes are 4-digit octal numbers (0000-7777) used by air traffic control
|
|
// for aircraft identification. They are transmitted in surveillance identity
|
|
// replies (DF5, DF21) and formatted as octal strings.
|
|
//
|
|
// Parameters:
|
|
// - data: Mode S identity reply message
|
|
//
|
|
// Returns 4-digit octal squawk code as a string (e.g., "1200", "7700").
|
|
func (d *Decoder) decodeSquawk(data []byte) string {
|
|
code := uint16(data[2])<<8 | uint16(data[3])
|
|
return fmt.Sprintf("%04o", code>>3)
|
|
}
|
|
|
|
// getAircraftCategory converts type code and category fields to human-readable descriptions.
|
|
//
|
|
// Aircraft categories are encoded in identification messages using:
|
|
// - Type Code (TC): Broad category group (1-4)
|
|
// - Category (CA): Specific category within the group (0-7)
|
|
//
|
|
// Categories include:
|
|
// - TC 1: Reserved
|
|
// - TC 2: Surface vehicles (emergency, service, obstacles)
|
|
// - TC 3: Light aircraft (gliders, balloons, UAVs, etc.)
|
|
// - TC 4: Aircraft by weight class and performance
|
|
//
|
|
// These categories help ATC and other aircraft understand the type of vehicle
|
|
// and its performance characteristics for separation and routing.
|
|
//
|
|
// Parameters:
|
|
// - tc: Type code (1-4) from identification message
|
|
// - ca: Category field (0-7) providing specific subtype
|
|
//
|
|
// Returns human-readable category description.
|
|
func (d *Decoder) getAircraftCategory(tc uint8, ca uint8) string {
|
|
switch tc {
|
|
case 1:
|
|
return "Reserved"
|
|
case 2:
|
|
switch ca {
|
|
case 1:
|
|
return "Surface Emergency Vehicle"
|
|
case 3:
|
|
return "Surface Service Vehicle"
|
|
case 4, 5, 6, 7:
|
|
return "Ground Obstruction"
|
|
default:
|
|
return "Surface Vehicle"
|
|
}
|
|
case 3:
|
|
switch ca {
|
|
case 1:
|
|
return "Glider/Sailplane"
|
|
case 2:
|
|
return "Lighter-than-Air"
|
|
case 3:
|
|
return "Parachutist/Skydiver"
|
|
case 4:
|
|
return "Ultralight/Hang-glider"
|
|
case 6:
|
|
return "UAV"
|
|
case 7:
|
|
return "Space Vehicle"
|
|
default:
|
|
return "Light Aircraft"
|
|
}
|
|
case 4:
|
|
switch ca {
|
|
case 1:
|
|
return "Light < 7000kg"
|
|
case 2:
|
|
return "Medium 7000-34000kg"
|
|
case 3:
|
|
return "Large 34000-136000kg"
|
|
case 4:
|
|
return "High Vortex Large"
|
|
case 5:
|
|
return "Heavy > 136000kg"
|
|
case 6:
|
|
return "High Performance"
|
|
case 7:
|
|
return "Rotorcraft"
|
|
default:
|
|
return "Aircraft"
|
|
}
|
|
default:
|
|
return "Unknown"
|
|
}
|
|
}
|
|
|
|
// decodeStatus extracts emergency and priority status from aircraft status messages.
|
|
//
|
|
// Aircraft status messages (TC 28) contain emergency and priority codes that
|
|
// indicate special situations requiring ATC attention:
|
|
// - General emergency (Mayday)
|
|
// - Medical emergency (Lifeguard)
|
|
// - Minimum fuel
|
|
// - Communication failure
|
|
// - Unlawful interference (hijack)
|
|
// - Downed aircraft
|
|
//
|
|
// These codes trigger special handling by ATC and emergency services.
|
|
//
|
|
// Parameters:
|
|
// - data: Extended squitter message containing status information
|
|
// - aircraft: Aircraft struct to update with emergency status
|
|
func (d *Decoder) decodeStatus(data []byte, aircraft *Aircraft) {
|
|
subtype := data[4] & 0x07
|
|
|
|
if subtype == 1 {
|
|
// Emergency/priority status
|
|
emergency := (data[5] >> 5) & 0x07
|
|
switch emergency {
|
|
case 0:
|
|
aircraft.Emergency = "None"
|
|
case 1:
|
|
aircraft.Emergency = "General Emergency"
|
|
case 2:
|
|
aircraft.Emergency = "Lifeguard/Medical"
|
|
case 3:
|
|
aircraft.Emergency = "Minimum Fuel"
|
|
case 4:
|
|
aircraft.Emergency = "No Communications"
|
|
case 5:
|
|
aircraft.Emergency = "Unlawful Interference"
|
|
case 6:
|
|
aircraft.Emergency = "Downed Aircraft"
|
|
}
|
|
}
|
|
}
|
|
|
|
// decodeTargetState extracts autopilot and flight management system settings.
|
|
//
|
|
// Target state and status messages (TC 29) contain information about:
|
|
// - Selected altitude (MCP/FCU setting)
|
|
// - Barometric pressure setting (QNH)
|
|
// - Autopilot engagement status
|
|
// - Flight management system intentions
|
|
//
|
|
// This information helps ATC understand pilot intentions and autopilot settings,
|
|
// improving situational awareness and conflict prediction.
|
|
//
|
|
// Parameters:
|
|
// - data: Extended squitter message containing target state data
|
|
// - aircraft: Aircraft struct to update with autopilot settings
|
|
func (d *Decoder) decodeTargetState(data []byte, aircraft *Aircraft) {
|
|
// Selected altitude
|
|
altBits := uint16(data[5]&0x7F)<<4 | uint16(data[6])>>4
|
|
if altBits != 0 {
|
|
aircraft.SelectedAltitude = int(altBits)*32 - 32
|
|
}
|
|
|
|
// Barometric pressure setting
|
|
baroBits := uint16(data[7])<<1 | uint16(data[8])>>7
|
|
if baroBits != 0 {
|
|
aircraft.BaroSetting = float64(baroBits)*0.8 + 800
|
|
}
|
|
}
|
|
|
|
// decodeOperationalStatus extracts navigation accuracy and system capability information.
|
|
//
|
|
// Operational status messages (TC 31) contain:
|
|
// - Navigation Accuracy Category for Position (NACp): Position accuracy
|
|
// - Navigation Accuracy Category for Velocity (NACv): Velocity accuracy
|
|
// - Surveillance Integrity Level (SIL): System integrity confidence
|
|
//
|
|
// These parameters help receiving systems assess data quality and determine
|
|
// appropriate separation standards for the aircraft.
|
|
//
|
|
// Parameters:
|
|
// - data: Extended squitter message containing operational status
|
|
// - aircraft: Aircraft struct to update with accuracy indicators
|
|
func (d *Decoder) decodeOperationalStatus(data []byte, aircraft *Aircraft) {
|
|
// Navigation accuracy categories
|
|
aircraft.NACp = (data[7] >> 4) & 0x0F
|
|
aircraft.NACv = data[7] & 0x0F
|
|
aircraft.SIL = (data[8] >> 6) & 0x03
|
|
|
|
// Calculate combined signal quality from NACp, NACv, and SIL
|
|
d.calculateSignalQuality(aircraft)
|
|
}
|
|
|
|
// decodeSurfacePosition extracts position and movement data for aircraft on the ground.
|
|
//
|
|
// Surface position messages (TC 5-8) are used for airport ground movement tracking:
|
|
// - Ground speed and movement direction
|
|
// - Track angle (direction of movement)
|
|
// - CPR-encoded position (same algorithm as airborne)
|
|
// - On-ground flag is automatically set
|
|
//
|
|
// Ground Movement Encoding:
|
|
// - Speed ranges from stationary to 175+ knots in non-linear increments
|
|
// - Track is encoded in 128 discrete directions (2.8125° resolution)
|
|
// - Position uses the same CPR encoding as airborne messages
|
|
//
|
|
// Parameters:
|
|
// - data: Extended squitter message containing surface position data
|
|
// - aircraft: Aircraft struct to update with ground movement information
|
|
func (d *Decoder) decodeSurfacePosition(data []byte, aircraft *Aircraft) {
|
|
aircraft.OnGround = true
|
|
|
|
// Movement
|
|
movement := uint8(data[4]&0x07)<<4 | uint8(data[5])>>4
|
|
if movement > 0 && movement < 125 {
|
|
aircraft.GroundSpeed = int(math.Round(d.decodeGroundSpeed(movement)))
|
|
}
|
|
|
|
// Track
|
|
trackValid := (data[5] >> 3) & 0x01
|
|
if trackValid != 0 {
|
|
trackBits := uint16(data[5]&0x07)<<4 | uint16(data[6])>>4
|
|
aircraft.Track = int(math.Round(float64(trackBits) * 360.0 / 128.0))
|
|
}
|
|
|
|
// CPR position (similar to airborne)
|
|
cprLat := uint32(data[6]&0x03)<<15 | uint32(data[7])<<7 | uint32(data[8])>>1
|
|
cprLon := uint32(data[8]&0x01)<<16 | uint32(data[9])<<8 | uint32(data[10])
|
|
oddFlag := (data[6] >> 2) & 0x01
|
|
|
|
// Store CPR values for later decoding (protected by mutex)
|
|
d.mu.Lock()
|
|
if oddFlag == 1 {
|
|
d.cprOddLat[aircraft.ICAO24] = float64(cprLat) / 131072.0
|
|
d.cprOddLon[aircraft.ICAO24] = float64(cprLon) / 131072.0
|
|
} else {
|
|
d.cprEvenLat[aircraft.ICAO24] = float64(cprLat) / 131072.0
|
|
d.cprEvenLon[aircraft.ICAO24] = float64(cprLon) / 131072.0
|
|
}
|
|
d.mu.Unlock()
|
|
|
|
d.decodeCPRPosition(aircraft)
|
|
}
|
|
|
|
// decodeGroundSpeed converts the surface movement field to ground speed in knots.
|
|
//
|
|
// Surface movement is encoded in non-linear ranges optimized for typical
|
|
// ground operations:
|
|
// - 0: No movement information
|
|
// - 1: Stationary
|
|
// - 2-8: 0.125-1.0 kt (fine resolution for slow movement)
|
|
// - 9-12: 1.0-2.0 kt (taxi speeds)
|
|
// - 13-38: 2.0-15.0 kt (normal taxi)
|
|
// - 39-93: 15.0-70.0 kt (high speed taxi/runway)
|
|
// - 94-108: 70.0-100.0 kt (takeoff/landing roll)
|
|
// - 109-123: 100.0-175.0 kt (high speed operations)
|
|
// - 124: >175 kt
|
|
//
|
|
// Parameters:
|
|
// - movement: 7-bit movement field from surface position message
|
|
//
|
|
// Returns ground speed in knots, or 0 for invalid/no movement.
|
|
func (d *Decoder) decodeGroundSpeed(movement uint8) float64 {
|
|
if movement == 1 {
|
|
return 0
|
|
} else if movement >= 2 && movement <= 8 {
|
|
return float64(movement-2)*0.125 + 0.125
|
|
} else if movement >= 9 && movement <= 12 {
|
|
return float64(movement-9)*0.25 + 1.0
|
|
} else if movement >= 13 && movement <= 38 {
|
|
return float64(movement-13)*0.5 + 2.0
|
|
} else if movement >= 39 && movement <= 93 {
|
|
return float64(movement-39)*1.0 + 15.0
|
|
} else if movement >= 94 && movement <= 108 {
|
|
return float64(movement-94)*2.0 + 70.0
|
|
} else if movement >= 109 && movement <= 123 {
|
|
return float64(movement-109)*5.0 + 100.0
|
|
} else if movement == 124 {
|
|
return 175.0
|
|
}
|
|
return 0
|
|
}
|
|
|
|
// decodeAllCallReply extracts capability and interrogator identifier from DF11 messages.
|
|
//
|
|
// DF11 All-Call Reply messages contain:
|
|
// - Capability (CA) field (3 bits): transponder capabilities and modes
|
|
// - Interrogator Identifier (II) field (4 bits): which radar interrogated
|
|
// - ICAO24 address (24 bits): aircraft identifier
|
|
//
|
|
// The capability field indicates transponder features and operational modes:
|
|
// - 0: Level 1 transponder
|
|
// - 1: Level 2 transponder
|
|
// - 2: Level 2+ transponder with additional capabilities
|
|
// - 3: Level 2+ transponder with enhanced surveillance
|
|
// - 4: Level 2+ transponder with enhanced surveillance and extended squitter
|
|
// - 5: Level 2+ transponder with enhanced surveillance, extended squitter, and enhanced surveillance capability
|
|
// - 6: Level 2+ transponder with enhanced surveillance, extended squitter, and enhanced surveillance capability
|
|
// - 7: Level 2+ transponder, downlink request value is 0, or the flight status is alert, SPI, or emergency
|
|
//
|
|
// Parameters:
|
|
// - data: 7-byte DF11 message
|
|
// - aircraft: Aircraft struct to populate
|
|
func (d *Decoder) decodeAllCallReply(data []byte, aircraft *Aircraft) {
|
|
if len(data) < 7 {
|
|
return
|
|
}
|
|
|
|
// Extract Capability (CA) - bits 6-8 of first byte
|
|
capability := (data[0] >> 0) & 0x07
|
|
|
|
// Extract Interrogator Identifier (II) - would be in control field if present
|
|
// For DF11, this information is typically implied by the interrogating radar
|
|
|
|
// Store transponder capability information in dedicated fields
|
|
aircraft.TransponderLevel = capability
|
|
switch capability {
|
|
case 0:
|
|
aircraft.TransponderCapability = "Level 1"
|
|
case 1:
|
|
aircraft.TransponderCapability = "Level 2"
|
|
case 2, 3:
|
|
aircraft.TransponderCapability = "Level 2+"
|
|
case 4, 5, 6:
|
|
aircraft.TransponderCapability = "Enhanced"
|
|
case 7:
|
|
aircraft.TransponderCapability = "Alert/Emergency"
|
|
}
|
|
}
|
|
|
|
// decodeMilitaryExtendedSquitter processes DF19 military extended squitter messages.
|
|
//
|
|
// DF19 messages have the same structure as DF17/18 ADS-B extended squitter but
|
|
// may contain military-specific type codes or enhanced data formats.
|
|
// This implementation treats them similarly to civilian extended squitter
|
|
// but could be extended for military-specific capabilities.
|
|
//
|
|
// Parameters:
|
|
// - data: 14-byte DF19 message
|
|
// - aircraft: Aircraft struct to populate
|
|
//
|
|
// Returns updated Aircraft struct or error for malformed messages.
|
|
func (d *Decoder) decodeMilitaryExtendedSquitter(data []byte, aircraft *Aircraft) (*Aircraft, error) {
|
|
if len(data) != 14 {
|
|
return nil, fmt.Errorf("invalid military extended squitter length: %d bytes", len(data))
|
|
}
|
|
|
|
// For now, treat military extended squitter similar to civilian
|
|
// Could be enhanced to handle military-specific type codes
|
|
return d.decodeExtendedSquitter(data, aircraft)
|
|
}
|
|
|
|
// decodeCommD extracts data from DF24 Comm-D Enhanced Length Messages.
|
|
//
|
|
// DF24 messages are variable-length data link communications that can contain:
|
|
// - Weather information and updates
|
|
// - Flight plan modifications
|
|
// - Controller-pilot data link messages
|
|
// - Air traffic management information
|
|
// - Future air navigation system data
|
|
//
|
|
// Due to the complexity and variety of DF24 message content, this implementation
|
|
// provides basic structure extraction. Full decoding would require extensive
|
|
// knowledge of specific data link protocols and message formats.
|
|
//
|
|
// Parameters:
|
|
// - data: Variable-length DF24 message (minimum 7 bytes)
|
|
// - aircraft: Aircraft struct to populate
|
|
func (d *Decoder) decodeCommD(data []byte, aircraft *Aircraft) {
|
|
if len(data) < 7 {
|
|
return
|
|
}
|
|
|
|
// DF24 messages contain variable data that would require protocol-specific decoding
|
|
// For now, we note that this is a data communication message but don't overwrite aircraft category
|
|
// Could set a separate field for message type if needed in the future
|
|
|
|
// The actual message content would require:
|
|
// - Protocol identifier extraction
|
|
// - Message type determination
|
|
// - Format-specific field extraction
|
|
// - Possible message reassembly for multi-part messages
|
|
//
|
|
// This could be extended based on specific requirements and available documentation
|
|
}
|
|
|
|
// calculateSignalQuality combines NACp, NACv, and SIL into an overall data quality assessment.
|
|
//
|
|
// This function provides a human-readable quality indicator that considers:
|
|
// - Position accuracy (NACp): How precise the aircraft's position data is
|
|
// - Velocity accuracy (NACv): How precise the speed/heading data is
|
|
// - Surveillance integrity (SIL): How reliable/trustworthy the data is
|
|
//
|
|
// The algorithm prioritizes integrity first (SIL), then position accuracy (NACp),
|
|
// then velocity accuracy (NACv) to provide a meaningful overall assessment.
|
|
//
|
|
// Quality levels:
|
|
// - "Excellent": High integrity with very precise position/velocity
|
|
// - "Good": Good integrity with reasonable precision
|
|
// - "Fair": Moderate quality suitable for tracking
|
|
// - "Poor": Low quality but still usable
|
|
// - "Unknown": No quality indicators available
|
|
//
|
|
// Parameters:
|
|
// - aircraft: Aircraft struct containing NACp, NACv, and SIL values
|
|
func (d *Decoder) calculateSignalQuality(aircraft *Aircraft) {
|
|
nacp := aircraft.NACp
|
|
nacv := aircraft.NACv
|
|
sil := aircraft.SIL
|
|
|
|
// If no quality indicators are available, don't set anything
|
|
if nacp == 0 && nacv == 0 && sil == 0 {
|
|
// Don't overwrite existing quality assessment
|
|
return
|
|
}
|
|
|
|
// Excellent: High integrity with high accuracy OR very high accuracy alone
|
|
if (sil >= 2 && nacp >= 9) || nacp >= 10 {
|
|
aircraft.SignalQuality = "Excellent"
|
|
return
|
|
}
|
|
|
|
// Good: Good integrity with moderate accuracy OR high accuracy alone
|
|
if (sil >= 2 && nacp >= 6) || (sil >= 1 && nacp >= 9) || nacp >= 8 {
|
|
aircraft.SignalQuality = "Good"
|
|
return
|
|
}
|
|
|
|
// Fair: Some integrity with basic accuracy OR moderate accuracy alone
|
|
if (sil >= 1 && nacp >= 3) || nacp >= 5 {
|
|
aircraft.SignalQuality = "Fair"
|
|
return
|
|
}
|
|
|
|
// Poor: Low but usable quality indicators
|
|
if sil > 0 || nacp >= 1 || nacv > 0 {
|
|
aircraft.SignalQuality = "Poor"
|
|
return
|
|
}
|
|
|
|
// Default fallback
|
|
aircraft.SignalQuality = ""
|
|
}
|