diff --git a/assets/static/css/style.css b/assets/static/css/style.css index 5389c2d..750603c 100644 --- a/assets/static/css/style.css +++ b/assets/static/css/style.css @@ -679,62 +679,4 @@ body { #aircraft-table td { padding: 0.5rem 0.25rem; } -} - -/* Squawk Code Styling */ -.squawk-emergency { - background: #dc3545; - color: white; - padding: 2px 6px; - border-radius: 4px; - font-weight: bold; - cursor: help; - border: 1px solid #b52532; -} - -.squawk-special { - background: #fd7e14; - color: white; - padding: 2px 6px; - border-radius: 4px; - font-weight: 500; - cursor: help; - border: 1px solid #e06911; -} - -.squawk-military { - background: #6c757d; - color: white; - padding: 2px 6px; - border-radius: 4px; - font-weight: 500; - cursor: help; - border: 1px solid #565e64; -} - -.squawk-standard { - background: #28a745; - color: white; - padding: 2px 6px; - border-radius: 4px; - font-weight: normal; - cursor: help; - border: 1px solid #1e7e34; -} - -/* Hover effects for squawk codes */ -.squawk-emergency:hover { - background: #c82333; -} - -.squawk-special:hover { - background: #e8590c; -} - -.squawk-military:hover { - background: #5a6268; -} - -.squawk-standard:hover { - background: #218838; } \ No newline at end of file diff --git a/assets/static/js/modules/ui-manager.js b/assets/static/js/modules/ui-manager.js index b891096..88c692c 100644 --- a/assets/static/js/modules/ui-manager.js +++ b/assets/static/js/modules/ui-manager.js @@ -122,7 +122,7 @@ export class UIManager { row.innerHTML = ` ${icao} ${aircraft.Callsign || '-'} - ${this.formatSquawk(aircraft)} + ${aircraft.Squawk || '-'} ${altitude ? `${altitude} ft` : '-'} ${aircraft.GroundSpeed || '-'} kt ${distance ? distance.toFixed(1) : '-'} km @@ -180,33 +180,6 @@ export class UIManager { return 'signal-poor'; } - formatSquawk(aircraft) { - if (!aircraft.Squawk) return '-'; - - // If we have a description, format it nicely - if (aircraft.SquawkDescription) { - // Check if it's an emergency code (contains warning emoji) - if (aircraft.SquawkDescription.includes('⚠️')) { - return `${aircraft.Squawk}`; - } - // Check if it's a special code (contains special emoji) - else if (aircraft.SquawkDescription.includes('🔸')) { - return `${aircraft.Squawk}`; - } - // Check if it's a military code (contains military emoji) - else if (aircraft.SquawkDescription.includes('🔰')) { - return `${aircraft.Squawk}`; - } - // Standard codes - else { - return `${aircraft.Squawk}`; - } - } - - // No description available, show just the code - return aircraft.Squawk; - } - updateSourceFilter() { const select = document.getElementById('source-filter'); if (!select) return; diff --git a/internal/merger/merger.go b/internal/merger/merger.go index 5fe3a80..7274219 100644 --- a/internal/merger/merger.go +++ b/internal/merger/merger.go @@ -29,7 +29,6 @@ import ( "skyview/internal/icao" "skyview/internal/modes" - "skyview/internal/squawk" ) const ( @@ -127,7 +126,6 @@ func (a *AircraftState) MarshalJSON() ([]byte, error) { Heading int `json:"Heading"` Category string `json:"Category"` Squawk string `json:"Squawk"` - SquawkDescription string `json:"SquawkDescription"` Emergency string `json:"Emergency"` OnGround bool `json:"OnGround"` Alert bool `json:"Alert"` @@ -175,7 +173,6 @@ func (a *AircraftState) MarshalJSON() ([]byte, error) { Heading: a.Aircraft.Heading, Category: a.Aircraft.Category, Squawk: a.Aircraft.Squawk, - SquawkDescription: a.Aircraft.SquawkDescription, Emergency: a.Aircraft.Emergency, OnGround: a.Aircraft.OnGround, Alert: a.Aircraft.Alert, @@ -271,7 +268,6 @@ type Merger struct { aircraft map[uint32]*AircraftState // ICAO24 -> merged aircraft state sources map[string]*Source // Source ID -> source information icaoDB *icao.Database // ICAO country lookup database - squawkDB *squawk.Database // Transponder code lookup database mu sync.RWMutex // Protects all maps and slices historyLimit int // Maximum history points to retain staleTimeout time.Duration // Time before aircraft considered stale (15 seconds) @@ -300,13 +296,10 @@ func NewMerger() (*Merger, error) { return nil, fmt.Errorf("failed to initialize ICAO database: %w", err) } - squawkDB := squawk.NewDatabase() - return &Merger{ aircraft: make(map[uint32]*AircraftState), sources: make(map[string]*Source), icaoDB: icaoDB, - squawkDB: squawkDB, historyLimit: 500, staleTimeout: 15 * time.Second, // Aircraft timeout - reasonable for ADS-B tracking updateMetrics: make(map[uint32]*updateMetric), @@ -542,8 +535,6 @@ func (m *Merger) mergeAircraftData(state *AircraftState, new *modes.Aircraft, so } if new.Squawk != "" { state.Squawk = new.Squawk - // Look up squawk description - state.SquawkDescription = m.squawkDB.FormatSquawkWithDescription(new.Squawk) } if new.Category != "" { state.Category = new.Category diff --git a/internal/modes/decoder.go b/internal/modes/decoder.go index 959fa19..e120abf 100644 --- a/internal/modes/decoder.go +++ b/internal/modes/decoder.go @@ -130,9 +130,8 @@ type Aircraft struct { 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) - SquawkDescription string // Human-readable description of transponder code + Category string // Aircraft category (size, type, performance) + Squawk string // 4-digit transponder squawk code (octal) // Status and Alerts Emergency string // Emergency/priority status description diff --git a/internal/squawk/squawk.go b/internal/squawk/squawk.go deleted file mode 100644 index 2647bd1..0000000 --- a/internal/squawk/squawk.go +++ /dev/null @@ -1,393 +0,0 @@ -// Package squawk provides transponder code lookup and interpretation functionality. -// -// This package implements a comprehensive database of transponder (squawk) codes -// used in aviation, providing textual descriptions and categorizations for -// common codes including emergency, operational, and special-purpose codes. -// -// The lookup system supports: -// - Emergency codes (7500, 7600, 7700) -// - Standard VFR/IFR operational codes -// - Military and special operations codes -// - Regional variations and custom codes -// -// Code categories help with UI presentation and priority handling, with -// emergency codes receiving special visual treatment in the interface. -package squawk - -import ( - "fmt" - "strconv" -) - -// CodeType represents the category of a transponder code for UI presentation -type CodeType int - -const ( - // Unknown represents codes not in the lookup database - Unknown CodeType = iota - // Emergency represents emergency situation codes (7500, 7600, 7700) - Emergency - // Standard represents common operational codes (1200, 7000, etc.) - Standard - // Military represents military operation codes - Military - // Special represents special purpose codes (SAR, firefighting, etc.) - Special -) - -// String returns the string representation of a CodeType -func (ct CodeType) String() string { - switch ct { - case Emergency: - return "Emergency" - case Standard: - return "Standard" - case Military: - return "Military" - case Special: - return "Special" - default: - return "Unknown" - } -} - -// CodeInfo contains detailed information about a transponder code -type CodeInfo struct { - Code string `json:"code"` // The transponder code (e.g., "7700") - Description string `json:"description"` // Human-readable description - Type CodeType `json:"type"` // Category of the code - Region string `json:"region"` // Geographic region (e.g., "Global", "ICAO", "US", "EU") - Priority int `json:"priority"` // Display priority (higher = more important) - Notes string `json:"notes"` // Additional context or usage notes -} - -// Database contains the transponder code lookup database -type Database struct { - codes map[string]*CodeInfo // Map of code -> CodeInfo -} - -// NewDatabase creates and initializes a new transponder code database -// with comprehensive coverage of common aviation transponder codes -func NewDatabase() *Database { - db := &Database{ - codes: make(map[string]*CodeInfo), - } - - // Initialize with standard transponder codes - db.loadStandardCodes() - - return db -} - -// loadStandardCodes populates the database with standard transponder codes -func (db *Database) loadStandardCodes() { - codes := []*CodeInfo{ - // Emergency Codes (Highest Priority) - { - Code: "7500", - Description: "Hijacking/Unlawful Interference", - Type: Emergency, - Region: "Global", - Priority: 100, - Notes: "Aircraft under unlawful interference or hijacking", - }, - { - Code: "7600", - Description: "Radio Failure", - Type: Emergency, - Region: "Global", - Priority: 95, - Notes: "Loss of radio communication capability", - }, - { - Code: "7700", - Description: "General Emergency", - Type: Emergency, - Region: "Global", - Priority: 90, - Notes: "General emergency situation requiring immediate attention", - }, - - // Standard VFR/IFR Codes - { - Code: "1200", - Description: "VFR - Visual Flight Rules", - Type: Standard, - Region: "US/Canada", - Priority: 10, - Notes: "Standard VFR code for uncontrolled airspace in North America", - }, - { - Code: "7000", - Description: "VFR - Visual Flight Rules", - Type: Standard, - Region: "ICAO/Europe", - Priority: 10, - Notes: "Standard VFR code for most ICAO regions", - }, - { - Code: "2000", - Description: "Uncontrolled Airspace", - Type: Standard, - Region: "Various", - Priority: 8, - Notes: "Used in some regions for uncontrolled airspace operations", - }, - { - Code: "0000", - Description: "No Transponder/Military", - Type: Military, - Region: "Global", - Priority: 5, - Notes: "No transponder assigned or military operations", - }, - { - Code: "1000", - Description: "Mode A/C Not Assigned", - Type: Standard, - Region: "Global", - Priority: 5, - Notes: "Transponder operating but no specific code assigned", - }, - - // Special Purpose Codes - { - Code: "1255", - Description: "Fire Fighting Aircraft", - Type: Special, - Region: "US", - Priority: 25, - Notes: "Aircraft engaged in firefighting operations", - }, - { - Code: "1277", - Description: "Search and Rescue", - Type: Special, - Region: "US", - Priority: 30, - Notes: "Search and rescue operations", - }, - { - Code: "7777", - Description: "Military Interceptor", - Type: Military, - Region: "US", - Priority: 35, - Notes: "Military interceptor aircraft", - }, - - // Military Ranges - { - Code: "4000", - Description: "Military Low Altitude", - Type: Military, - Region: "US", - Priority: 15, - Notes: "Military operations at low altitude (4000-4777 range)", - }, - { - Code: "0100", - Description: "Military Operations", - Type: Military, - Region: "Various", - Priority: 12, - Notes: "Military interceptor operations (0100-0777 range)", - }, - - // Additional Common Codes - { - Code: "1201", - Description: "VFR - High Altitude", - Type: Standard, - Region: "US", - Priority: 8, - Notes: "VFR operations above 12,500 feet MSL", - }, - { - Code: "4401", - Description: "Glider Operations", - Type: Special, - Region: "US", - Priority: 8, - Notes: "Glider and soaring aircraft operations", - }, - { - Code: "1202", - Description: "VFR - Above 12,500ft", - Type: Standard, - Region: "US", - Priority: 8, - Notes: "VFR flight above 12,500 feet requiring transponder", - }, - - // European Specific - { - Code: "7001", - Description: "Conspicuity Code A", - Type: Standard, - Region: "Europe", - Priority: 10, - Notes: "Enhanced visibility in European airspace", - }, - { - Code: "7004", - Description: "Aerobatic Flight", - Type: Special, - Region: "Europe", - Priority: 12, - Notes: "Aircraft engaged in aerobatic maneuvers", - }, - { - Code: "7010", - Description: "GAT in OAT Area", - Type: Special, - Region: "Europe", - Priority: 8, - Notes: "General Air Traffic operating in Other Air Traffic area", - }, - } - - // Add all codes to the database - for _, code := range codes { - db.codes[code.Code] = code - } -} - -// Lookup returns information about a given transponder code -// -// The method accepts both 4-digit strings and integers, automatically -// formatting them as needed. Returns nil if the code is not found in the database. -// -// Parameters: -// - code: Transponder code as string (e.g., "7700") or can be called with fmt.Sprintf -// -// Returns: -// - *CodeInfo: Detailed information about the code, or nil if not found -func (db *Database) Lookup(code string) *CodeInfo { - if info, exists := db.codes[code]; exists { - return info - } - return nil -} - -// LookupInt is a convenience method for looking up codes as integers -// -// Parameters: -// - code: Transponder code as integer (e.g., 7700) -// -// Returns: -// - *CodeInfo: Detailed information about the code, or nil if not found -func (db *Database) LookupInt(code int) *CodeInfo { - codeStr := fmt.Sprintf("%04d", code) - return db.Lookup(codeStr) -} - -// LookupHex is a convenience method for looking up codes from hex strings -// -// Transponder codes are sometimes transmitted in hex format. This method -// converts hex to decimal before lookup. -// -// Parameters: -// - hexCode: Transponder code as hex string (e.g., "1E14" for 7700) -// -// Returns: -// - *CodeInfo: Detailed information about the code, or nil if not found or invalid hex -func (db *Database) LookupHex(hexCode string) *CodeInfo { - // Convert hex to decimal - if decimal, err := strconv.ParseInt(hexCode, 16, 16); err == nil { - // Convert decimal to 4-digit octal representation (squawk codes are octal) - octal := fmt.Sprintf("%04o", decimal) - return db.Lookup(octal) - } - return nil -} - -// GetEmergencyCodes returns all emergency codes in the database -// -// Returns: -// - []*CodeInfo: Slice of all emergency codes, sorted by priority (highest first) -func (db *Database) GetEmergencyCodes() []*CodeInfo { - var emergencyCodes []*CodeInfo - - for _, info := range db.codes { - if info.Type == Emergency { - emergencyCodes = append(emergencyCodes, info) - } - } - - // Sort by priority (highest first) - for i := 0; i < len(emergencyCodes); i++ { - for j := i + 1; j < len(emergencyCodes); j++ { - if emergencyCodes[i].Priority < emergencyCodes[j].Priority { - emergencyCodes[i], emergencyCodes[j] = emergencyCodes[j], emergencyCodes[i] - } - } - } - - return emergencyCodes -} - -// IsEmergencyCode returns true if the given code represents an emergency situation -// -// Parameters: -// - code: Transponder code as string (e.g., "7700") -// -// Returns: -// - bool: True if the code is an emergency code -func (db *Database) IsEmergencyCode(code string) bool { - if info := db.Lookup(code); info != nil { - return info.Type == Emergency - } - return false -} - -// GetAllCodes returns all codes in the database -// -// Returns: -// - []*CodeInfo: Slice of all codes in the database -func (db *Database) GetAllCodes() []*CodeInfo { - codes := make([]*CodeInfo, 0, len(db.codes)) - for _, info := range db.codes { - codes = append(codes, info) - } - return codes -} - -// AddCustomCode allows adding custom transponder codes to the database -// -// This is useful for regional codes or organization-specific codes not -// included in the standard database. -// -// Parameters: -// - info: CodeInfo struct with the custom code information -func (db *Database) AddCustomCode(info *CodeInfo) { - db.codes[info.Code] = info -} - -// FormatSquawkWithDescription returns a formatted string combining code and description -// -// Formats transponder codes for display with appropriate emergency indicators. -// Emergency codes are prefixed with warning symbols for visual emphasis. -// -// Parameters: -// - code: Transponder code as string (e.g., "7700") -// -// Returns: -// - string: Formatted string like "7700 (⚠️ EMERGENCY - General)" or "1200 (VFR - Visual Flight Rules)" -func (db *Database) FormatSquawkWithDescription(code string) string { - info := db.Lookup(code) - if info == nil { - return code // Return just the code if no description available - } - - switch info.Type { - case Emergency: - return fmt.Sprintf("%s (⚠️ EMERGENCY - %s)", code, info.Description) - case Special: - return fmt.Sprintf("%s (🔸 %s)", code, info.Description) - case Military: - return fmt.Sprintf("%s (🔰 %s)", code, info.Description) - default: - return fmt.Sprintf("%s (%s)", code, info.Description) - } -} \ No newline at end of file diff --git a/internal/squawk/squawk_test.go b/internal/squawk/squawk_test.go deleted file mode 100644 index 6a008db..0000000 --- a/internal/squawk/squawk_test.go +++ /dev/null @@ -1,209 +0,0 @@ -package squawk - -import ( - "testing" -) - -func TestNewDatabase(t *testing.T) { - db := NewDatabase() - if db == nil { - t.Fatal("NewDatabase() returned nil") - } - - if len(db.codes) == 0 { - t.Error("Database should contain pre-loaded codes") - } -} - -func TestEmergencyCodes(t *testing.T) { - db := NewDatabase() - - emergencyCodes := []string{"7500", "7600", "7700"} - - for _, code := range emergencyCodes { - info := db.Lookup(code) - if info == nil { - t.Errorf("Emergency code %s not found", code) - continue - } - - if info.Type != Emergency { - t.Errorf("Code %s should be Emergency type, got %s", code, info.Type) - } - - if !db.IsEmergencyCode(code) { - t.Errorf("IsEmergencyCode(%s) should return true", code) - } - } -} - -func TestStandardCodes(t *testing.T) { - db := NewDatabase() - - testCases := []struct { - code string - description string - }{ - {"1200", "VFR - Visual Flight Rules"}, - {"7000", "VFR - Visual Flight Rules"}, - {"1000", "Mode A/C Not Assigned"}, - } - - for _, tc := range testCases { - info := db.Lookup(tc.code) - if info == nil { - t.Errorf("Standard code %s not found", tc.code) - continue - } - - if info.Description != tc.description { - t.Errorf("Code %s: expected description %q, got %q", - tc.code, tc.description, info.Description) - } - } -} - -func TestLookupInt(t *testing.T) { - db := NewDatabase() - - // Test integer lookup - info := db.LookupInt(7700) - if info == nil { - t.Fatal("LookupInt(7700) returned nil") - } - - if info.Code != "7700" { - t.Errorf("Expected code '7700', got '%s'", info.Code) - } - - if info.Type != Emergency { - t.Errorf("Code 7700 should be Emergency type, got %s", info.Type) - } -} - -func TestLookupHex(t *testing.T) { - db := NewDatabase() - - // 7700 in octal is 3840 in decimal, which is F00 in hex - // However, squawk codes are transmitted differently in different formats - // For now, test with a simple hex conversion - - // Test invalid hex - info := db.LookupHex("INVALID") - if info != nil { - t.Error("LookupHex with invalid hex should return nil") - } -} - -func TestFormatSquawkWithDescription(t *testing.T) { - db := NewDatabase() - - testCases := []struct { - code string - expected string - }{ - {"7700", "7700 (⚠️ EMERGENCY - General Emergency)"}, - {"1200", "1200 (VFR - Visual Flight Rules)"}, - {"1277", "1277 (🔸 Search and Rescue)"}, - {"0000", "0000 (🔰 No Transponder/Military)"}, - {"9999", "9999"}, // Unknown code should return just the code - } - - for _, tc := range testCases { - result := db.FormatSquawkWithDescription(tc.code) - if result != tc.expected { - t.Errorf("FormatSquawkWithDescription(%s): expected %q, got %q", - tc.code, tc.expected, result) - } - } -} - -func TestGetEmergencyCodes(t *testing.T) { - db := NewDatabase() - - emergencyCodes := db.GetEmergencyCodes() - if len(emergencyCodes) != 3 { - t.Errorf("Expected 3 emergency codes, got %d", len(emergencyCodes)) - } - - // Check that they're sorted by priority (highest first) - for i := 1; i < len(emergencyCodes); i++ { - if emergencyCodes[i-1].Priority < emergencyCodes[i].Priority { - t.Error("Emergency codes should be sorted by priority (highest first)") - } - } -} - -func TestAddCustomCode(t *testing.T) { - db := NewDatabase() - - customCode := &CodeInfo{ - Code: "1234", - Description: "Test Custom Code", - Type: Special, - Region: "Test", - Priority: 50, - Notes: "This is a test custom code", - } - - db.AddCustomCode(customCode) - - info := db.Lookup("1234") - if info == nil { - t.Fatal("Custom code not found after adding") - } - - if info.Description != "Test Custom Code" { - t.Errorf("Custom code description mismatch: expected %q, got %q", - "Test Custom Code", info.Description) - } -} - -func TestCodeTypeString(t *testing.T) { - testCases := []struct { - codeType CodeType - expected string - }{ - {Unknown, "Unknown"}, - {Emergency, "Emergency"}, - {Standard, "Standard"}, - {Military, "Military"}, - {Special, "Special"}, - } - - for _, tc := range testCases { - result := tc.codeType.String() - if result != tc.expected { - t.Errorf("CodeType.String(): expected %q, got %q", tc.expected, result) - } - } -} - -func TestGetAllCodes(t *testing.T) { - db := NewDatabase() - - allCodes := db.GetAllCodes() - if len(allCodes) == 0 { - t.Error("GetAllCodes() should return non-empty slice") - } - - // Verify we can find known codes in the result - found7700 := false - found1200 := false - - for _, code := range allCodes { - if code.Code == "7700" { - found7700 = true - } - if code.Code == "1200" { - found1200 = true - } - } - - if !found7700 { - t.Error("Emergency code 7700 not found in GetAllCodes() result") - } - if !found1200 { - t.Error("Standard code 1200 not found in GetAllCodes() result") - } -} \ No newline at end of file