diff --git a/assets/static/js/modules/aircraft-manager.js b/assets/static/js/modules/aircraft-manager.js index 37e9e0f..ac92646 100644 --- a/assets/static/js/modules/aircraft-manager.js +++ b/assets/static/js/modules/aircraft-manager.js @@ -362,6 +362,10 @@ export class AircraftManager {
Type: ${type}
+ ${aircraft.TransponderCapability ? ` +
+ Transponder: ${aircraft.TransponderCapability} +
` : ''}
diff --git a/internal/merger/merger.go b/internal/merger/merger.go index f561d95..fdb0c52 100644 --- a/internal/merger/merger.go +++ b/internal/merger/merger.go @@ -112,29 +112,31 @@ func (a *AircraftState) MarshalJSON() ([]byte, error) { // Create a struct that mirrors AircraftState but with ICAO24 as string return json.Marshal(&struct { // From embedded modes.Aircraft - ICAO24 string `json:"ICAO24"` - Callsign string `json:"Callsign"` - Latitude float64 `json:"Latitude"` - Longitude float64 `json:"Longitude"` - Altitude int `json:"Altitude"` - BaroAltitude int `json:"BaroAltitude"` - GeomAltitude int `json:"GeomAltitude"` - VerticalRate int `json:"VerticalRate"` - GroundSpeed int `json:"GroundSpeed"` - Track int `json:"Track"` - Heading int `json:"Heading"` - Category string `json:"Category"` - Squawk string `json:"Squawk"` - Emergency string `json:"Emergency"` - OnGround bool `json:"OnGround"` - Alert bool `json:"Alert"` - SPI bool `json:"SPI"` - NACp uint8 `json:"NACp"` - NACv uint8 `json:"NACv"` - SIL uint8 `json:"SIL"` - SelectedAltitude int `json:"SelectedAltitude"` - SelectedHeading float64 `json:"SelectedHeading"` - BaroSetting float64 `json:"BaroSetting"` + ICAO24 string `json:"ICAO24"` + Callsign string `json:"Callsign"` + Latitude float64 `json:"Latitude"` + Longitude float64 `json:"Longitude"` + Altitude int `json:"Altitude"` + BaroAltitude int `json:"BaroAltitude"` + GeomAltitude int `json:"GeomAltitude"` + VerticalRate int `json:"VerticalRate"` + GroundSpeed int `json:"GroundSpeed"` + Track int `json:"Track"` + Heading int `json:"Heading"` + Category string `json:"Category"` + Squawk string `json:"Squawk"` + Emergency string `json:"Emergency"` + OnGround bool `json:"OnGround"` + Alert bool `json:"Alert"` + SPI bool `json:"SPI"` + NACp uint8 `json:"NACp"` + NACv uint8 `json:"NACv"` + SIL uint8 `json:"SIL"` + TransponderCapability string `json:"TransponderCapability"` + TransponderLevel uint8 `json:"TransponderLevel"` + SelectedAltitude int `json:"SelectedAltitude"` + SelectedHeading float64 `json:"SelectedHeading"` + BaroSetting float64 `json:"BaroSetting"` // From AircraftState Sources map[string]*SourceData `json:"sources"` @@ -156,29 +158,31 @@ func (a *AircraftState) MarshalJSON() ([]byte, error) { Flag string `json:"flag"` }{ // Copy all fields from Aircraft - ICAO24: fmt.Sprintf("%06X", a.Aircraft.ICAO24), - Callsign: a.Aircraft.Callsign, - Latitude: a.Aircraft.Latitude, - Longitude: a.Aircraft.Longitude, - Altitude: a.Aircraft.Altitude, - BaroAltitude: a.Aircraft.BaroAltitude, - GeomAltitude: a.Aircraft.GeomAltitude, - VerticalRate: a.Aircraft.VerticalRate, - GroundSpeed: a.Aircraft.GroundSpeed, - Track: a.Aircraft.Track, - Heading: a.Aircraft.Heading, - Category: a.Aircraft.Category, - Squawk: a.Aircraft.Squawk, - Emergency: a.Aircraft.Emergency, - OnGround: a.Aircraft.OnGround, - Alert: a.Aircraft.Alert, - SPI: a.Aircraft.SPI, - NACp: a.Aircraft.NACp, - NACv: a.Aircraft.NACv, - SIL: a.Aircraft.SIL, - SelectedAltitude: a.Aircraft.SelectedAltitude, - SelectedHeading: a.Aircraft.SelectedHeading, - BaroSetting: a.Aircraft.BaroSetting, + ICAO24: fmt.Sprintf("%06X", a.Aircraft.ICAO24), + Callsign: a.Aircraft.Callsign, + Latitude: a.Aircraft.Latitude, + Longitude: a.Aircraft.Longitude, + Altitude: a.Aircraft.Altitude, + BaroAltitude: a.Aircraft.BaroAltitude, + GeomAltitude: a.Aircraft.GeomAltitude, + VerticalRate: a.Aircraft.VerticalRate, + GroundSpeed: a.Aircraft.GroundSpeed, + Track: a.Aircraft.Track, + Heading: a.Aircraft.Heading, + Category: a.Aircraft.Category, + Squawk: a.Aircraft.Squawk, + Emergency: a.Aircraft.Emergency, + OnGround: a.Aircraft.OnGround, + Alert: a.Aircraft.Alert, + SPI: a.Aircraft.SPI, + NACp: a.Aircraft.NACp, + NACv: a.Aircraft.NACv, + SIL: a.Aircraft.SIL, + TransponderCapability: a.Aircraft.TransponderCapability, + TransponderLevel: a.Aircraft.TransponderLevel, + SelectedAltitude: a.Aircraft.SelectedAltitude, + SelectedHeading: a.Aircraft.SelectedHeading, + BaroSetting: a.Aircraft.BaroSetting, // Copy all fields from AircraftState Sources: a.Sources, diff --git a/internal/modes/decoder.go b/internal/modes/decoder.go index a8d5afb..f2ab232 100644 --- a/internal/modes/decoder.go +++ b/internal/modes/decoder.go @@ -138,6 +138,10 @@ type Aircraft struct { 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) + // Autopilot/Flight Management SelectedAltitude int // MCP/FCU selected altitude in feet SelectedHeading float64 // MCP/FCU selected heading in degrees @@ -231,12 +235,29 @@ func (d *Decoder) Decode(data []byte) (*Aircraft, error) { } switch df { + case DF0: + // Short Air-Air Surveillance (ACAS) + aircraft.Altitude = d.decodeAltitude(data) + // Additional ACAS-specific data could be extracted here 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) + // Additional ACAS-specific data could be extracted here 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) } return aircraft, nil @@ -940,3 +961,106 @@ func (d *Decoder) decodeGroundSpeed(movement uint8) float64 { } 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 Transponder" + case 1: + aircraft.TransponderCapability = "Level 2 Transponder" + case 2, 3: + aircraft.TransponderCapability = "Level 2+ Transponder" + case 4, 5, 6: + aircraft.TransponderCapability = "Enhanced Transponder" + case 7: + aircraft.TransponderCapability = "Alert/Emergency Status" + } +} + +// 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 +}