diff --git a/main/traffic.go b/main/traffic.go index 68a40dea..3a4dcedb 100644 --- a/main/traffic.go +++ b/main/traffic.go @@ -13,6 +13,7 @@ import ( "bufio" "encoding/hex" "encoding/json" + "fmt" "log" "math" "net" @@ -75,7 +76,8 @@ const ( type TrafficInfo struct { Icao_addr uint32 - Tail string + Reg string // Registration. Calculated from Icao_addr for civil aircraft of US registry. + Tail string // Callsign. Transmitted by aircraft. Emitter_category uint8 // Formatted using GDL90 standard, e.g. in a Mode ES report, A7 becomes 0x07, B0 becomes 0x08, etc. OnGround bool // Air-ground status. On-ground is "true". Addr_type uint8 // UAT address qualifier. Used by GDL90 format, so translations for ES TIS-B/ADS-R are needed. @@ -349,6 +351,12 @@ func parseDownlinkReport(s string, signalLevel int) { ti.Last_seen = stratuxClock.Time // need to initialize to current stratuxClock so it doesn't get cut before we have a chance to populate a position message ti.Icao_addr = icao_addr ti.ExtrapolatedPosition = false + + thisReg, validReg := icao2faa(icao_addr) + if validReg { + ti.Reg = thisReg + ti.Tail = thisReg + } } ti.Addr_type = addr_type @@ -650,6 +658,12 @@ func esListen() { ti.Icao_addr = icao ti.ExtrapolatedPosition = false ti.Last_source = TRAFFIC_SOURCE_1090ES + + thisReg, validReg := icao2faa(icao) + if validReg { + ti.Reg = thisReg + ti.Tail = thisReg + } } ti.SignalLevel = 10 * math.Log10(newTi.SignalLevel) @@ -1000,6 +1014,104 @@ func updateDemoTraffic(icao uint32, tail string, relAlt float32, gs float64, off } } +/* + icao2faa() : Converts 24-bit Mode S addresses to N-numbers. + + Input: uint32 representing the Mode S address. Valid range for + translation is 0xA00001 - 0xADF7C7, inclusive. + + Values outside the range A000001-AFFFFFF are flagged as non-US. + Values between ADF7C8 - AFFFFF are allocated to the United States, + but are not used for aicraft on the civil registry. These could be + military, other public aircraft, or (future use) temporary + anonymous addresses. + + Output: + string: String containing the decoded tail number (if decoding succeeded), + "NON-US" (for non-US allocation), and "MIL" for non-civil US allocation. + + bool: True if the Mode S address successfully translated to an + N number. False for all other conditions. +*/ + +func icao2faa(icao_addr uint32) (string, bool) { + // Initialize local variables + base34alphabet := string("ABCDEFGHJKLMNPQRSTUVWXYZ0123456789") + faaOffset := uint32(0xA00001) + tail := "" + + // Discard non-US ICAO codes + if (icao_addr < 0xA00001) || (icao_addr > 0xAFFFFF) { + //fmt.Printf("%X is a non-US address.\n", icao_addr) + return "NON-US", false + } + + // Discard addresses that are not assigned to aircraft on the civil registry (public aircraft: USAF, USN, USMC, USCG, ANG, FBI, etc.) + if icao_addr > 0xADF7C7 { + //fmt.Printf("%X is a US aircraft, but not on the civil registry.\n", icao_addr) + return "MIL", false + } + + serial := int32(icao_addr - faaOffset) + // First digit + a := (serial / 101711) + 1 + + // Second digit + a_remainder := serial % 101711 + b := ((a_remainder + 9510) / 10111) - 1 + + // Third digit + b_remainder := (a_remainder + 9510) % 10111 + c := ((b_remainder + 350) / 951) - 1 + + // This next bit is more convoluted. First, figure out if we're using the "short" method of + // decoding the last two digits (two letters, one letter and one blank, or two blanks). + // This will be the case if digit "B" or "C" are calculated as negative, or if c_remainder + // is less than 601. + + c_remainder := (b_remainder + 350) % 951 + var d, e int32 + + if (b >= 0) && (c >= 0) && (c_remainder > 600) { // alphanumeric decoding method + d = 24 + (c_remainder-601)/35 + e = (c_remainder - 601) % 35 + + } else { // two-letter decoding method + if (b < 0) || (c < 0) { + c_remainder -= 350 // otherwise " " == 350, "A " == 351, "AA" == 352, etc. + } + + d = (c_remainder - 1) / 25 + e = (c_remainder - 1) % 25 + + if e < 0 { + d -= 1 + e += 25 + } + } + + a_char := fmt.Sprintf("%d", a) + var b_char, c_char, d_char, e_char string + + if b >= 0 { + b_char = fmt.Sprintf("%d", b) + } + + if b >= 0 && c >= 0 { + c_char = fmt.Sprintf("%d", c) + } + + if d > -1 { + d_char = string(base34alphabet[d]) + if e > 0 { + e_char = string(base34alphabet[e-1]) + } + } + + tail = "N" + a_char + b_char + c_char + d_char + e_char + return tail, true +} + func initTraffic() { traffic = make(map[uint32]TrafficInfo) seenTraffic = make(map[uint32]bool) diff --git a/web/plates/js/traffic.js b/web/plates/js/traffic.js index 18eda13a..0c8f26e7 100755 --- a/web/plates/js/traffic.js +++ b/web/plates/js/traffic.js @@ -59,6 +59,7 @@ function TrafficCtrl($rootScope, $scope, $state, $http, $interval) { } new_traffic.icao = obj.Icao_addr.toString(16).toUpperCase(); new_traffic.tail = obj.Tail; + new_traffic.reg = obj.Reg; if (obj.Squawk == 0) { new_traffic.squawk = "----"; } else { diff --git a/web/plates/traffic.html b/web/plates/traffic.html index 4c5dc501..bdae42bd 100755 --- a/web/plates/traffic.html +++ b/web/plates/traffic.html @@ -9,7 +9,8 @@
- Callsign + Callsign + Tail Num Code Squawk Location @@ -29,11 +30,15 @@
- + {{aircraft.addr_symb}} {{aircraft.tail}} {{aircraft.addr_symb}} [--N/A--] + + + {{aircraft.addr_symb}} {{aircraft.reg}} + {{aircraft.addr_symb}} [--N/A--] - + {{aircraft.icao}}{{aircraft.addr_type == 3 ? " (TFID)" : ""}} 000{{aircraft.squawk}} @@ -60,19 +65,26 @@
@@ -85,7 +97,8 @@
- Callsign + Callsign + Tail Num Code Squawk
@@ -102,9 +115,13 @@
- + {{aircraft.addr_symb}} {{aircraft.tail}} {{aircraft.addr_symb}} [--N/A--] + + + {{aircraft.addr_symb}} {{aircraft.reg}} + {{aircraft.addr_symb}} [--N/A--] {{aircraft.icao}}{{aircraft.addr_type == 3 ? " (TFID)" : ""}} 000{{aircraft.squawk}}