diff options
author | Arturs Artamonovs <arturs.artamonovs@protonmail.com> | 2024-06-05 09:09:36 +0100 |
---|---|---|
committer | Arturs Artamonovs <arturs.artamonovs@protonmail.com> | 2024-06-05 09:09:36 +0100 |
commit | 86c489c3c8ab1fa46fdcae022289f414f2de04cd (patch) | |
tree | fa1ec80a9b8bdbd544a5ae266c1139664d7e0cce | |
parent | f400051582a005e4a64377c21866b6cc11eeaeec (diff) | |
download | ADSBDecoder-86c489c3c8ab1fa46fdcae022289f414f2de04cd.tar.gz ADSBDecoder-86c489c3c8ab1fa46fdcae022289f414f2de04cd.zip |
Decoder part
-rw-r--r-- | ADSBDecoder/Decoder.swift | 236 | ||||
-rw-r--r-- | ADSBDecoder/Query.swift | 55 | ||||
-rw-r--r-- | ADSBDecoder/main.swift | 119 |
3 files changed, 409 insertions, 1 deletions
diff --git a/ADSBDecoder/Decoder.swift b/ADSBDecoder/Decoder.swift index 425797a..95f798b 100644 --- a/ADSBDecoder/Decoder.swift +++ b/ADSBDecoder/Decoder.swift @@ -6,3 +6,239 @@ // 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 + + 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) + + if (tc_byte == 4) { + let msg = ADSBTypeCode4(bindata[4...10]) + print("=====ADSB MESSSGE 04 =======") + print(msg) + print("============================") + } else if (tc_byte == 11) { + let msg = ADSBTypeCode11(bindata[4...10]) + print("=====ADSB MESSSGE 11 =======") + print(msg) + print("============================") + } else if (tc_byte == 19) { + print("Byte 19") + } else { + print("Unknow TC byte") + /* + //Decode Movement Field + let mov_byte = (bindata[4]&0x7) + (bindata[5]>>5) + print(String(format: "mov %02x", mov_byte)) + + //Heading Bit + let heading_bit = (bindata[5]>>3)&0x1 + print(String(format: "heading %02x", heading_bit)) + + //Heading direction + let heading_direction = (bindata[5]&0xF)<<3+(bindata[6]>>4)&0xF + print(String(format: "hdir %02x", heading_direction)) + + //CPR Odd/Even + let cpr_odd_even = (bindata[6]>>2)&0x1; + print(String(format: "cpr %02x", cpr_odd_even)) + + //CPR lat + let cpr_lat = (bindata[6]&0x7)<<14 + (bindata[7]<<7) + (bindata[8]>>1) + print(String(format: "cpr lat %06x", cpr_lat)) + + //CPR lon + let cpr_lon = ((bindata[8]&0x1)<<16) + (bindata[9]<<8) + (bindata[10]) + print(String(format: "cpr lon %06x", cpr_lon)) + */ + } + } +} + +class ADSBTypeCode4: 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 + } +} + +class ADSBTypeCode11: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) \n" + description += "Lat \(Latitude) Long \(Longitude)" + return description + } +} + +class DataFormat18 { + init(_ adsb_data: String) { + print("Dataformat: 18") + print(adsb_data) + } +} diff --git a/ADSBDecoder/Query.swift b/ADSBDecoder/Query.swift index 4906221..99d9075 100644 --- a/ADSBDecoder/Query.swift +++ b/ADSBDecoder/Query.swift @@ -6,3 +6,58 @@ // 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 = "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 + } +} diff --git a/ADSBDecoder/main.swift b/ADSBDecoder/main.swift index fa84962..a324a0f 100644 --- a/ADSBDecoder/main.swift +++ b/ADSBDecoder/main.swift @@ -6,6 +6,123 @@ // import Foundation +import ArgumentParser +import RegexBuilder -print("Hello, World!") +//return true if file excists +func checkIfFileExists(_ fname: String) -> Bool { + let fm = FileManager.default + if fm.fileExists(atPath: fname) { + return true + } + return false +} +//get current run directory +func getCurrentDirPath() -> String { + return Process().currentDirectoryPath +} + +struct CommandLineArgs: ParsableCommand { + @Option(name: .shortAndLong) var inputfile: String + @Flag(name: .shortAndLong) var debug:Bool = false + @Flag(name: .shortAndLong) var version:Bool = false + @Flag(name: .shortAndLong) var show_stats:Bool = false +} + +let args = CommandLineArgs.parseOrExit() + +let fileUrl = URL(fileURLWithPath:args.inputfile) +print("File location [\(fileUrl.absoluteString)]") + +//check if file excists +if (checkIfFileExists(fileUrl.path) == false) { + print("Supplied path \(fileUrl.path) doesnt exists") + exit(1) +} + +//load the file with adsb data +var adsb_source = "" +do { + adsb_source = try String(contentsOfFile: fileUrl.path) + print("Loaded \(adsb_source.count) bytes") +} catch { + print("Couldn't load text from a file \(fileUrl.path)") + exit(1) +} + + + +let matchADSBLong = Regex { + Anchor.startOfLine + "*" + + Repeat( + CharacterClass( + ("a"..."f"), + ("0"..."9") + ) + ,count:28) + + ";" +} + +let matchADSBShort = Regex { + Anchor.startOfLine + "*" + Repeat( + CharacterClass( + ("a"..."f"), + ("0"..."9") + ) + ,count:14) + ";" +} + +//gather stats +var q_df = QueryDF() +var q_dftc = QueryDF17TC() + +//parse line by line +var cnt=0 +for line in adsb_source.components(separatedBy: .newlines) { + var found=false + //print("\(cnt) \(line)") + //cnt += 1 + if let tokenMatch = try matchADSBLong.prefixMatch(in: line) { + //print("\(String(tokenMatch.output))") + found = true + let str = String(tokenMatch.output) + let startIndex = str.index(str.startIndex, offsetBy: 1) + let endIndex = str.index(str.endIndex, offsetBy: -2) + let decoder = Decoder(String(str[startIndex...endIndex])) + if decoder.DataFormat == 17 { + if let d17 = decoder.getDataFormat17() { + //print(d17) + q_dftc.addTC(d17.TypeCode) + } + } else { + + } + q_df.addDF(decoder.DataFormat) + }; + + if let tokenMatch = try matchADSBShort.prefixMatch(in: line) { + print("\(tokenMatch.output)") + found = true + }; + + if (found == false) { + print("Unknown adsb data line \(line)") + } + + +} + + +if args.show_stats { + print("----STAT----") + //q_df.showStat() + print(q_df) + print(q_dftc) +} |