diff options
| author | Arturs Artamonovs <arturs.artamonovs@protonmail.com> | 2026-09-24 11:02:35 +0100 |
|---|---|---|
| committer | Arturs Artamonovs <arturs.artamonovs@protonmail.com> | 2026-09-24 11:02:35 +0100 |
| commit | 505b23afc68ecbb63b8f0a7d44c0b0ad063d5cfd (patch) | |
| tree | d38b29a0cf23041571eb08fd79f49fdad8deb85f /ADSBDecoderFramework | |
| parent | 056ee0c3355db995d7ea571e0635ebe8b09bf8ee (diff) | |
| download | ADSBDecoder-505b23afc68ecbb63b8f0a7d44c0b0ad063d5cfd.tar.gz ADSBDecoder-505b23afc68ecbb63b8f0a7d44c0b0ad063d5cfd.zip | |
rearrange source, move common parts to framework
Diffstat (limited to 'ADSBDecoderFramework')
| -rw-r--r-- | ADSBDecoderFramework/ADSBDecoderFramework.docc/ADSBDecoderFramework.md | 13 | ||||
| -rw-r--r-- | ADSBDecoderFramework/ADSBDecoderFramework.swift | 9 | ||||
| -rw-r--r-- | ADSBDecoderFramework/ADSBRegEx.swift | 87 | ||||
| -rw-r--r-- | ADSBDecoderFramework/Configs.swift | 10 | ||||
| -rw-r--r-- | ADSBDecoderFramework/Decoder.swift | 249 | ||||
| -rw-r--r-- | ADSBDecoderFramework/PositionDecoder.swift | 221 | ||||
| -rw-r--r-- | ADSBDecoderFramework/Query.swift | 126 |
7 files changed, 715 insertions, 0 deletions
diff --git a/ADSBDecoderFramework/ADSBDecoderFramework.docc/ADSBDecoderFramework.md b/ADSBDecoderFramework/ADSBDecoderFramework.docc/ADSBDecoderFramework.md new file mode 100644 index 0000000..588e73d --- /dev/null +++ b/ADSBDecoderFramework/ADSBDecoderFramework.docc/ADSBDecoderFramework.md @@ -0,0 +1,13 @@ +# ``ADSBDecoderFramework`` + +<!--@START_MENU_TOKEN@-->Summary<!--@END_MENU_TOKEN@--> + +## Overview + +<!--@START_MENU_TOKEN@-->Text<!--@END_MENU_TOKEN@--> + +## Topics + +### <!--@START_MENU_TOKEN@-->Group<!--@END_MENU_TOKEN@--> + +- <!--@START_MENU_TOKEN@-->``Symbol``<!--@END_MENU_TOKEN@-->
\ No newline at end of file diff --git a/ADSBDecoderFramework/ADSBDecoderFramework.swift b/ADSBDecoderFramework/ADSBDecoderFramework.swift new file mode 100644 index 0000000..c2407a0 --- /dev/null +++ b/ADSBDecoderFramework/ADSBDecoderFramework.swift @@ -0,0 +1,9 @@ +// +// ADSBDecoderFramework.swift +// ADSBDecoderFramework +// +// Created by Jacky Jack on 24/09/2026. +// + +import Foundation + diff --git a/ADSBDecoderFramework/ADSBRegEx.swift b/ADSBDecoderFramework/ADSBRegEx.swift new file mode 100644 index 0000000..2841467 --- /dev/null +++ b/ADSBDecoderFramework/ADSBRegEx.swift @@ -0,0 +1,87 @@ +// +// ADSBRegEx.swift +// ADSBDecoder +// +// Created by Jacky Jack on 30/06/2024. +// + +import Foundation +import RegexBuilder +/* +let matchADSBLong = Regex { + Anchor.startOfLine + "*" + + Repeat( + CharacterClass( + ("a"..."f"), + ("0"..."9") + ) + ,count:28) + + ";" +}*/ +class MatchADSBLong { + let matchADSBLong = Regex { + Anchor.startOfLine + "*" + + Repeat( + CharacterClass( + ("a"..."f"), + ("0"..."9") + ) + ,count:28) + + ";" + } + + func prefixMatch(_ line: String) -> Regex<Regex<Substring>.RegexOutput>.Match? { + do { + if let match = try matchADSBLong.prefixMatch(in: line) { + return match + } + } catch { + return nil + } + return nil + } +} + +/* +let matchADSBShort = Regex { + Anchor.startOfLine + "*" + Repeat( + CharacterClass( + ("a"..."f"), + ("0"..."9") + ) + ,count:14) + ";" +}*/ + +class MatchADSBShort { + let matchADSBShort = Regex { + Anchor.startOfLine + "*" + Repeat( + CharacterClass( + ("a"..."f"), + ("0"..."9") + ) + ,count:14) + ";" + } + + func prefixMatch(_ line: String) -> Regex<Regex<Substring>.RegexOutput>.Match? { + do { + if let match = try matchADSBShort.prefixMatch(in: line) { + return match + } + } catch { + return nil + } + return nil + } +} diff --git a/ADSBDecoderFramework/Configs.swift b/ADSBDecoderFramework/Configs.swift new file mode 100644 index 0000000..a42e745 --- /dev/null +++ b/ADSBDecoderFramework/Configs.swift @@ -0,0 +1,10 @@ +// +// Configs.swift +// ADSBDecoder +// +// Created by Jacky Jack on 09/07/2024. +// + +import Foundation + +let decoder_debug_mode = false diff --git a/ADSBDecoderFramework/Decoder.swift b/ADSBDecoderFramework/Decoder.swift new file mode 100644 index 0000000..a9724b6 --- /dev/null +++ b/ADSBDecoderFramework/Decoder.swift @@ -0,0 +1,249 @@ +// +// Decoder.swift +// ADSBDecoder +// +// Created by Jacky Jack on 28/05/2024. +// + +import Foundation + +func BarometricAltitudeFeat(_ altitude: UInt16) -> Int { + let QBit = (altitude>>4)&0x1 + if QBit == 1 { + //remove qbit + let part1:UInt16 = altitude&0xf + let part2:UInt16 = (altitude>>1)&0x7ff0 + let altitude25 = part1 + part2 + //print("altitude2 \(altitude25) ") + return Int(altitude25)*25-1000 + } + return Int(altitude)*100-1000 +} + +class Decoder { + var adsb_data: String = "" + var DataFormat: UInt32 = 0; + + init (_ adsb_data: String) { + //print(adsb_data) + self.adsb_data = adsb_data + //get the first 8 bits as integer + let startI = adsb_data.startIndex + let endI = adsb_data.index(startI, offsetBy: 1) + let firstByteString = adsb_data[startI...endI] + //print("\(firstByteString)") + if let ControlMsg = UInt32(firstByteString, radix: 16) { + //print("ControlMsg = \(ControlMsg)") + let CM_DataFormat = (ControlMsg&0xF8)>>3 + DataFormat = CM_DataFormat + //let CM_TranspoderCapability = ControlMsg&(0x7) + } + //print("Data Format \(DataFormat)") + } + + func getDataFormat17() -> DataFormat17? { + + if (DataFormat != 17) { + return nil + } + //let startI = adsb_data.index(adsb_data.startIndex, offsetBy: 1) + //let endI = adsb_data.index(adsb_data.endIndex, offsetBy: -1) + //let adsbData = adsb_data[startI...endI] + let ret:DataFormat17 = DataFormat17(adsb_data) + + + + return ret; + } + + func getDataFormat18() { + print("Not implemented") + } + +} + +func ICAOAlphabet(_ code: UInt8) -> String { + if ((code>=1) && (code<=26)) { + return String(UnicodeScalar(code+64)) + } else if ((code >= 48)&&(code<=57)) { + return String(UnicodeScalar(code)) + } else if (code==32) { + return " " + } + return "#" +} + +func ICAO2String(_ b1: UInt8, _ b2: UInt8, _ b3: UInt8, _ b4: UInt8, _ b5: UInt8, _ b6: UInt8, _ b7: UInt8, _ b8: UInt8) -> String { + return ICAOAlphabet(b1)+ICAOAlphabet(b2)+ICAOAlphabet(b3)+ICAOAlphabet(b4)+ICAOAlphabet(b5)+ICAOAlphabet(b6)+ICAOAlphabet(b7)+ICAOAlphabet(b8) +} + +class DataFormat17 { + + let DataFormat=17 //0:5 + var Capability=0 //5:7 + var AddressAnnounced=0 //8:31 + var TypeCode=0 //32:36 + var MovementField=0//37:43 + var HeadingBit=0//44 + var HeadingField=0//45:51 + //var CPROddEven=0//53 + //var CPRlat=0//54:70 + //var CPRlon=0//71:87 + var ParityIntegrity=0//88-111 + + //all avaliable message types + var messageIdentification:ADSBTypeCodeIndentification? = nil + var messageAirbornPositon:ADSBTypeCodeAirbonePositon? = nil + + init(_ adsb_data: String) { + //print("Dataformat: 17!") + //print(adsb_data) + + var bindata:[UInt8] = [] + + var startN = adsb_data.startIndex + var endN = adsb_data.index(startN, offsetBy: 1) + var count=0//start index value + while (count<adsb_data.count) { + let u8 = UInt8(adsb_data[startN...endN], radix: 16)! + //print(adsb_data[startN...endN]) + bindata.append(u8) + count += 2 + if (count<adsb_data.count) { + startN = adsb_data.index(startN, offsetBy: 2) + endN = adsb_data.index(startN, offsetBy: 1) + } + } + //print(bindata) + + //Decode Capability + let cap = (bindata[0]>>1)&0x7 + //print(String(format: "cap %02x", cap)) + Capability = Int(cap) + + //Decode Address Announcement + let address_ann = UInt32(bindata[1])<<16 + UInt32(bindata[2])<<8 + UInt32(bindata[3]) + //print(String(format: "address %06x", address_ann)) + + AddressAnnounced = Int(address_ann) + + //Decode Type Code + let tc_byte = bindata[4] >> 3 + //print(String(format: "tc %02d", tc_byte)) + + TypeCode = Int(tc_byte) + + //aircraft indentification and category + if (tc_byte == 4) { + let msg = ADSBTypeCodeIndentification(bindata[4...10]) + if decoder_debug_mode { + print("=====ADSB MESSSGE 04 =======") + print(msg) + print("============================") + } + + messageIdentification = msg + //airborn position + } else if ((tc_byte >= 8) && (tc_byte <= 18)) { + let msg = ADSBTypeCodeAirbonePositon(bindata[4...10]) + if decoder_debug_mode { + print(String(format:"=====ADSB MESSSGE %02d ======= AA:%04d", tc_byte, AddressAnnounced)) + print(msg) + print("============================") + } + + messageAirbornPositon = msg + //airborn velocity + } else if (tc_byte == 19) { + if decoder_debug_mode { + print("=====ADSB MESSSGE 19 =======") + print("=====VELOCITY =======") + } + } else { + if decoder_debug_mode { + print("=====ADSB MESSSGE UNKNOWN =======") + } + } + } +} + +class ADSBTypeCodeIndentification: CustomStringConvertible { + var TypeCode:Int = 4 + var Category:Int = 0 + var ICAOName:String + + init(_ bindata:ArraySlice<UInt8>) { + let cat = bindata[4]&0x7 + Category = Int(cat) + let char_0 = bindata[5]>>2 + let char_1 = (bindata[5]&0x3)<<4 + bindata[6]>>4 + let char_2 = (bindata[6]&0xf)<<2 + (bindata[7]>>6)&0x3 + let char_3 = (bindata[7])&0x3f + let char_4 = bindata[8]>>2 + let char_5 = (bindata[8]&0x3)<<4 + bindata[9]>>4 + let char_6 = (bindata[9]&0xf)<<2 + (bindata[10]>>6) + let char_7 = bindata[10]&0x3f + + //print(char_0, char_1, char_2,char_3,char_4,char_5,char_6,char_7) + ICAOName = ICAO2String(char_0, char_1, char_2, char_3, char_4, char_5, char_6, char_7) + //print("ICAO name \(ICAOName)") + } + + var description: String { + let description = "TypeCode \(TypeCode) Cat \(Category) Flight name \(ICAOName)" + return description + } +} + +//DF17 message types codes 8 to 18 +class ADSBTypeCodeAirbonePositon:CustomStringConvertible { + var TypeCode:Int = 11 + var SurveillanceStatus: Int = 0 + var SingleAntennaFlag: Int = 0 + var Altitude: Int = 0 + var Time: Int = 0 + var CPRFormat: Int = 0 + var Latitude: Int = 0 + var Longitude: Int = 0 + + init(_ bindata:ArraySlice<UInt8>) { + let ss = (bindata[4]>>1)&(0x3) + SurveillanceStatus = Int(ss) + let saf = bindata[5]&0x1 + SingleAntennaFlag = Int(saf) + let altitude = UInt16(bindata[5])<<4 + (UInt16(bindata[6])>>4)&0xf + //print(altitude) + Altitude = BarometricAltitudeFeat(altitude) + let time = (bindata[6]>>3)&0x1 + Time = Int(time) + let cpr = (bindata[6]>>2)&0x1 + CPRFormat = Int(cpr) + let lat:UInt32 = UInt32(bindata[6]&0x3)<<15 + UInt32(bindata[7])<<7 + UInt32(bindata[8]>>1) + Latitude = Int(lat) + let lon:UInt32 = UInt32(bindata[8]&0x1)<<16 + UInt32(bindata[9])<<8 + UInt32(bindata[10]) + Longitude = Int(lon) + } + + var description: String { + var description = "SS \(SurveillanceStatus) SAF \(SingleAntennaFlag) Altitude \(Altitude)ft \n" + description += "Time \(Time) CPR \(CPRFormat)" + if (CPRFormat == 0) { + description += "even" + } else { + description += "odd" + } + description += "\n" + description += "Lat \(Latitude) Long \(Longitude)" + return description + } +} + +class DataFormat19:CustomStringConvertible { + init(_ bindata:ArraySlice<UInt8>) { + print("Dataformat: 19") + } + + var description: String { + return "" + } +} diff --git a/ADSBDecoderFramework/PositionDecoder.swift b/ADSBDecoderFramework/PositionDecoder.swift new file mode 100644 index 0000000..85b9d49 --- /dev/null +++ b/ADSBDecoderFramework/PositionDecoder.swift @@ -0,0 +1,221 @@ +// +// PositionDecoder.swift +// ADSBDecoder +// +// Created by Jacky Jack on 14/06/2024. +// + +import Foundation +import Collections + +struct PositionData { + var cpr_lat: UInt32 + var cpr_long: UInt32 + var even: Bool + var cprtime: Int64 +} + +func cpr_mod(_ a: Int, _ b: Int) -> Int { + var res:Int = a % b; + if (res < 0) { + res += b + } + return res; +} + +/* The NL function uses the precomputed table from 1090-WP-9-14 */ +func cpr_NLFunction(_ _lat: Double) -> Int { + var lat:Double = _lat + if (lat < 0) { + lat = -lat + } /* Table is simmetric about the equator. */ + if (lat < 10.47047130) {return 59} + if (lat < 14.82817437) {return 58} + if (lat < 18.18626357) {return 57} + if (lat < 21.02939493) {return 56} + if (lat < 23.54504487) {return 55} + if (lat < 25.82924707) {return 54} + if (lat < 27.93898710) {return 53} + if (lat < 29.91135686) {return 52} + if (lat < 31.77209708) {return 51} + if (lat < 33.53993436) {return 50} + if (lat < 35.22899598) {return 49} + if (lat < 36.85025108) {return 48} + if (lat < 38.41241892) {return 47} + if (lat < 39.92256684) {return 46} + if (lat < 41.38651832) {return 45} + if (lat < 42.80914012) {return 44} + if (lat < 44.19454951) {return 43} + if (lat < 45.54626723) {return 42} + if (lat < 46.86733252) {return 41} + if (lat < 48.16039128) {return 40} + if (lat < 49.42776439) {return 39} + if (lat < 50.67150166) {return 38} + if (lat < 51.89342469) {return 37} + if (lat < 53.09516153) {return 36} + if (lat < 54.27817472) {return 35} + if (lat < 55.44378444) {return 34} + if (lat < 56.59318756) {return 33} + if (lat < 57.72747354) {return 32} + if (lat < 58.84763776) {return 31} + if (lat < 59.95459277) {return 30} + if (lat < 61.04917774) {return 29} + if (lat < 62.13216659) {return 28} + if (lat < 63.20427479) {return 27} + if (lat < 64.26616523) {return 26} + if (lat < 65.31845310) {return 25} + if (lat < 66.36171008) {return 24} + if (lat < 67.39646774) {return 23} + if (lat < 68.42322022) {return 22} + if (lat < 69.44242631) {return 21} + if (lat < 70.45451075) {return 20} + if (lat < 71.45986473) {return 19} + if (lat < 72.45884545) {return 18} + if (lat < 73.45177442) {return 17} + if (lat < 74.43893416) {return 16} + if (lat < 75.42056257) {return 15} + if (lat < 76.39684391) {return 14} + if (lat < 77.36789461) {return 13} + if (lat < 78.33374083) {return 12} + if (lat < 79.29428225) {return 11} + if (lat < 80.24923213) {return 10} + if (lat < 81.19801349) {return 9} + if (lat < 82.13956981) {return 8} + if (lat < 83.07199445) {return 7} + if (lat < 83.99173563) {return 6} + if (lat < 84.89166191) {return 5} + if (lat < 85.75541621) {return 4} + if (lat < 86.53536998) {return 3} + if (lat < 87.00000000) {return 2} + else {return 1} +} + +func cpr_NFunction(_ lat: Double, _ isodd: Int) -> Int { + var nl:Int = cpr_NLFunction(lat) - isodd; + if (nl < 1) { + nl = 1 + } + return nl; +} + +func cpr_DlonFunction(_ lat: Double, _ isodd: Int) -> Double { + return 360.0 / Double(cpr_NFunction(lat, isodd)); +} + +func mstime() -> Int64 { + var tv:timeval = timeval(tv_sec: 0, tv_usec: 0) + var mst:Int64=0; + + gettimeofday(&tv, nil); + mst = Int64(tv.tv_sec)*1000; + mst += Int64(tv.tv_usec)/1000; + return mst; +} + +class PositionDecoder { + + var long: Double = 0.0 + var lat: Double = 0.0 + var ready: Bool = false + var newposition: Bool = false + + //initialise first values so this values is nonsence + var queue: Deque<PositionData> = [] + + init() { + + } + + func addEvenPosition(_ cpr_lat: UInt32,_ cpr_long: UInt32, _ cprtime: Int64) { + queue.append(PositionData(cpr_lat:cpr_lat, cpr_long:cpr_long, even:true, cprtime: cprtime)) + } + + func addOddPosition(_ cpr_lat: UInt32,_ cpr_long: UInt32, _ cprtime: Int64) { + queue.append(PositionData(cpr_lat:cpr_lat,cpr_long:cpr_long,even:false, cprtime: cprtime)) + } + + func getPosition() -> (Double, Double)? { + if (ready) { + return (lat, long) + } + return nil + } + + //TODO: allways adds to queue newer frees the queue + func calcPosition() -> Bool { + if (queue.count > 2) { + let el1 = queue[queue.count-1] + let el2 = queue[queue.count-2] + if (el1.even == el2.even) { + //ready = false + return false + } + } else { + ready = false + print("Position queue to short to calculate values") + return false + } + + //last to elements are evan and odd + let el1 = queue[queue.count-1] + let el2 = queue[queue.count-2] + let cpr_even = el1.even ? el1 : el2 + let cpr_odd = (!el1.even) ? el1 : el2 + + //print("Position queue is ready to calculate location \(cpr_even) \(cpr_odd)") + // from here https://github.com/antirez/dump1090/blob/master/dump1090.c#L1718 + let AirDlat0:Double = 360.0/60.0 + let AirDlat1:Double = 360.0/59.0 + + let lat0 = Int(cpr_even.cpr_lat) + let lat1 = Int(cpr_odd.cpr_lat) + let lon0 = Int(cpr_even.cpr_long) + let lon1 = Int(cpr_odd.cpr_long) + + //latitude index + let j:Int = Int(floor(((59.0*Double(lat0) - 60.0*Double(lat1)) / 131072.0) + 0.5)) + + var rlat0:Double = AirDlat0 * (Double(cpr_mod(j,60)) + Double(lat0) / 131072.0) + var rlat1:Double = AirDlat1 * (Double(cpr_mod(j,59)) + Double(lat1) / 131072.0) + + if (rlat0 >= 270.0) { + rlat0 -= 360.0 + } + if (rlat1 >= 270.0) { + rlat1 -= 360.0 + } + + /* Check that both are in the same latitude zone, or abort. */ + if (cpr_NLFunction(rlat0) != cpr_NLFunction(rlat1)) { + print("Not same lat?!") + return false + } + + /* Compute ni and the longitude index m */ + if (cpr_even.cprtime > cpr_odd.cprtime) { + /* Use even packet. */ + let ni:Int = cpr_NFunction(rlat0,0); + let m:Int = Int( + floor((((Double(lon0) * (Double(cpr_NLFunction(rlat0))-1.0)) - + (Double(lon1) * Double(cpr_NLFunction(rlat0)))) / 131072.0) + 0.5) + ); + self.long = Double(cpr_DlonFunction(rlat0,0)) * (Double(cpr_mod(m,ni))+Double(lon0)/131072.0); + self.lat = rlat0; + } else { + /* Use odd packet. */ + let ni:Int = cpr_NFunction(rlat1,1); + let m:Int = Int(floor((((Double(lon0) * (Double(cpr_NLFunction(rlat1))-1.0)) - + (Double(lon1) * Double(cpr_NLFunction(rlat1)))) / 131072.0) + 0.5)); + self.long = cpr_DlonFunction(rlat1,1) * (Double(cpr_mod(m,ni))+Double(lon1)/131072.0); + self.lat = rlat1; + } + if (self.long > 180) { + self.long -= 360 + } + + ready = true + return true + } + + +} diff --git a/ADSBDecoderFramework/Query.swift b/ADSBDecoderFramework/Query.swift new file mode 100644 index 0000000..a92d919 --- /dev/null +++ b/ADSBDecoderFramework/Query.swift @@ -0,0 +1,126 @@ +// +// Query.swift +// ADSBDecoder +// +// Created by Jacky Jack on 31/05/2024. +// + +import Foundation + +class QueryDF: CustomStringConvertible { + + //max I see is 24 + var DFlist:[Int] = Array(repeating: 0, count: 30) + + init() { + + } + + func addDF(_ dataformat: UInt32) { + DFlist[Int(dataformat)] += 1 + } + + func showStat() { + print("DataFormat Count") + for i in 0...(DFlist.count)-1 { + if DFlist[i] != 0 { + print(String(format:" %02d: %05d", i, DFlist[i])) + } + } + } + + var description: String { + var description = "DataFormat Count\n" + for i in 0...(DFlist.count)-1 { + if DFlist[i] != 0 { + description += String(format:" %02d: %04d\n", i, DFlist[i]) + } + } + return description + } +} + +class QueryDF17TC: CustomStringConvertible { + var TClist:[Int] = Array(repeating: 0, count: 32) + + init() { + + } + + func addTC(_ typecode: Int) { + TClist[typecode] += 1 + } + + var description: String { + var description = "DF17 TypeCode Count\n" + for i in 0...(TClist.count-1) { + if TClist[i] != 0 { + description += String(format: " %02d: %04d\n", i, TClist[i]) + } + } + return description + } +} + +class QueryDecodedMessages: CustomStringConvertible { + + var decoded:Int = 0 + var total:Int = 0 + + init() { + + } + + func addDecoded() { + decoded += 1 + total += 1 + } + + func addUndecoded() { + total = total + 1 + } + + var description: String { + var description = "Total messages:\n" + description += "Decoded:\(decoded)\n" + description += "Total :\(total) \(String(format:"%02.01f",Float(Float(decoded)/Float(total)*100.0)))%\n" + return description + } + +} + + +class QueryDF17TC_decoded: CustomStringConvertible { + var TClist_decoded:[Int] = Array(repeating: 0, count: 32) + var TClist_total:[Int] = Array(repeating: 0, count: 32) + + init() { + + } + + func addDecoded(_ typecode: Int) { + TClist_decoded[typecode] += 1 + TClist_total[typecode] += 1 + } + + func addUndecode(_ typecode: Int) { + TClist_total[typecode] += 1 + } + + var description: String { + var description = "" + var decoded:Int = 0 + var total:Int = 0 + for i in 0...(TClist_decoded.count-1) { + total += TClist_total[i] + decoded += TClist_decoded[i] + if TClist_total[i] != 0 { + description += String(format: " %02d: %04d\n", i, TClist_decoded[i]) + } + } + description += String(format:"Total %d/%d %.1f",decoded,total,Double(Double(decoded)/Double(total))*100) + description += "%\n" + return description + } +} + |
