Module: PWN::SDR::Decoder::ADSB
- Defined in:
- lib/pwn/sdr/decoder/adsb.rb
Overview
ADS-B (1090 MHz Mode-S / 978 MHz UAT) true-air decoder.
Prefer PWN::FFI::RTLSdr,AdalmPluto,HackRF at ≥2 Msps and run a pure-Ruby Mode-S preamble correlator + 112-bit PPM slicer over magnitude samples. Falls back to Base.run_detector energy mode when no I/Q source is available. Offline SBS-1 CSV → .parse_line.
Defined Under Namespace
Classes: DemodIQ
Constant Summary collapse
- SBS_FIELDS =
%i[ msg_type tx_type session_id aircraft_id icao24 flight_id date_gen time_gen date_log time_log callsign altitude_ft ground_speed_kt track_deg lat lon vertical_rate_fpm squawk alert emergency spi on_ground ].freeze
- PREAMBLE =
8 μs Mode-S preamble at 2 Msps → 16 samples: 1 0 1 0 0 0 0 1 0 1 0 0 0 0 0 0
[1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0].map(&:to_f).freeze
- SAMPLES_PER_US =
@ 2 Msps
2- MODE_S_CRC_POLY =
CRC-24 (Mode-S) generator 0x1FFF409 (poly over GF(2), 24-bit)
0x1FFF409
Class Method Summary collapse
- .ais_char(opts = {}) ⇒ Object
-
.authors ⇒ Object
- Author(s)
0day Inc.
-
.crc24(opts = {}) ⇒ Object
- Supported Method Parameters
crc = PWN::SDR::Decoder::ADSB.crc24(bits: Array<0|1>) CRC over all bits except the final 24 (which hold the parity).
-
.crc_ok?(opts = {}) ⇒ Boolean
- Supported Method Parameters
ok = PWN::SDR::Decoder::ADSB.crc_ok?(bits: Array<0|1>).
-
.decode(opts = {}) ⇒ Object
- Supported Method Parameters
PWN::SDR::Decoder::ADSB.decode( freq_obj: 'required - freq_obj returned from PWN::SDR::GQRX.init_freq' ).
-
.decode_modes(opts = {}) ⇒ Object
- Supported Method Parameters
h = PWN::SDR::Decoder::ADSB.decode_modes(bits: Array<0|1> of length 56 or 112).
-
.help ⇒ Object
Display Usage for this Module.
- .modes_altitude(opts = {}) ⇒ Object
-
.parse_line(opts = {}) ⇒ Object
- Supported Method Parameters
PWN::SDR::Decoder::ADSB.parse_line(line: 'MSG,3,1,1,ABCDEF,...').
Class Method Details
.ais_char(opts = {}) ⇒ Object
183 184 185 186 187 |
# File 'lib/pwn/sdr/decoder/adsb.rb', line 183 public_class_method def self.ais_char(opts = {}) code = opts[:code] table = '#ABCDEFGHIJKLMNOPQRSTUVWXYZ##### ###############0123456789######' table[code] || ' ' end |
.authors ⇒ Object
- Author(s)
0day Inc. support@0dayinc.com
248 249 250 |
# File 'lib/pwn/sdr/decoder/adsb.rb', line 248 public_class_method def self. "AUTHOR(S):\n 0day Inc. <support@0dayinc.com>\n" end |
.crc24(opts = {}) ⇒ Object
- Supported Method Parameters
crc = PWN::SDR::Decoder::ADSB.crc24(bits: Array<0|1>) CRC over all bits except the final 24 (which hold the parity).
119 120 121 122 123 124 125 126 127 128 129 130 131 132 |
# File 'lib/pwn/sdr/decoder/adsb.rb', line 119 public_class_method def self.crc24(opts = {}) bits = opts[:bits] || [] return nil if bits.length < 32 # Mode-S CRC-24: left-shift register fed by every message bit # (including the 24 parity bits). A valid frame leaves residual 0. reg = 0 bits.each do |b| reg <<= 1 reg |= (b & 1) reg ^= MODE_S_CRC_POLY if reg.anybits?(0x1000000) end reg & 0xFFFFFF end |
.crc_ok?(opts = {}) ⇒ Boolean
- Supported Method Parameters
ok = PWN::SDR::Decoder::ADSB.crc_ok?(bits: Array<0|1>)
137 138 139 140 141 142 |
# File 'lib/pwn/sdr/decoder/adsb.rb', line 137 public_class_method def self.crc_ok?(opts = {}) bits = opts[:bits] || [] return false unless [56, 112].include?(bits.length) crc24(bits: bits).zero? end |
.decode(opts = {}) ⇒ Object
- Supported Method Parameters
PWN::SDR::Decoder::ADSB.decode( freq_obj: 'required - freq_obj returned from PWN::SDR::GQRX.init_freq' )
207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 |
# File 'lib/pwn/sdr/decoder/adsb.rb', line 207 public_class_method def self.decode(opts = {}) freq_obj = opts[:freq_obj] hz = PWN::SDR.hz_to_i(freq: freq_obj[:freq]) uat = hz.between?(977_000_000, 979_000_000) proto = uat ? 'ADSB-UAT978' : 'ADSB-1090ES' rate = (opts[:sample_rate] || freq_obj[:iq_rate] || 2_000_000).to_i PWN::SDR::Decoder::Base.run_iq( freq_obj: freq_obj, protocol: proto, sample_rate: rate, source: opts[:source], file: opts[:file], demod: DemodIQ.new(rate: rate), note: 'Mode-S 2 Mbit/s PPM — true-air path uses RTL-SDR/Pluto I/Q; detector fallback characterises squitter density only.', describe: proc { |b| { modulation: 'PPM', frame_len_us: 120, classification: b[:duration_ms] < 5 ? 'squitter' : 'interrogation-train' } } ) end |
.decode_modes(opts = {}) ⇒ Object
- Supported Method Parameters
h = PWN::SDR::Decoder::ADSB.decode_modes(bits: Array<0|1> of length 56 or 112)
147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 |
# File 'lib/pwn/sdr/decoder/adsb.rb', line 147 public_class_method def self.decode_modes(opts = {}) bits = opts[:bits] || [] return nil unless [56, 112].include?(bits.length) # DF (5) + CA (3) + ICAO (24) + ... df = PWN::SDR::Decoder::DSP.bits_to_int(bits: bits[0, 5]) icao = format('%06X', PWN::SDR::Decoder::DSP.bits_to_int(bits: bits[8, 24])) out = { protocol: 'ADSB', df: df, icao24: icao, bits: bits.length, raw_hex: bits.each_slice(4).map { |n| PWN::SDR::Decoder::DSP.bits_to_int(bits: n).to_s(16) }.join.upcase } # DF17/18 ME field (56 bits starting at bit 32) if [17, 18].include?(df) && bits.length >= 88 tc = PWN::SDR::Decoder::DSP.bits_to_int(bits: bits[32, 5]) out[:type_code] = tc if tc.between?(1, 4) # aircraft identification — 8× 6-bit AIS chars cs = bits[40, 48].each_slice(6).map { |ch| ais_char(code: PWN::SDR::Decoder::DSP.bits_to_int(bits: ch)) }.join.strip out[:callsign] = cs elsif tc.between?(9, 18) || tc.between?(20, 22) out[:altitude_ft] = modes_altitude(bits12: bits[40, 12]) end end bits_s = [] bits_s << "ICAO=#{out[:icao24]}" bits_s << "DF=#{df}" bits_s << "CS=#{out[:callsign]}" if out[:callsign] bits_s << "ALT=#{out[:altitude_ft]}ft" if out[:altitude_ft] bits_s << "TC=#{out[:type_code]}" if out[:type_code] out[:summary] = "ADSB #{bits_s.join(' ')}" out end |
.help ⇒ Object
Display Usage for this Module
254 255 256 257 258 259 260 261 262 263 264 265 266 267 |
# File 'lib/pwn/sdr/decoder/adsb.rb', line 254 public_class_method def self.help puts "USAGE (true-air I/Q via PWN::FFI + detector fallback): #{self}.decode( freq_obj: 'required - freq_obj returned from PWN::SDR::GQRX.init_freq', source: 'optional - :auto|:rtlsdr|:adalm_pluto|:file', file: 'optional - .cu8/.cs16 capture at ≥2 Msps' ) #{self}.decode_modes(bits: [0,1,...]) # 56/112 Mode-S bits #{self}.parse_line(line: 'MSG,3,1,1,ABCDEF,1,...') #{self}.authors " end |
.modes_altitude(opts = {}) ⇒ Object
189 190 191 192 193 194 195 196 197 198 199 200 |
# File 'lib/pwn/sdr/decoder/adsb.rb', line 189 public_class_method def self.modes_altitude(opts = {}) bits12 = opts[:bits12] return nil unless bits12.is_a?(Array) && bits12.length == 12 # Gillham / 25 ft encoding — simplified (bit 7 is Q) q = bits12[7] if q == 1 n = PWN::SDR::Decoder::DSP.bits_to_int(bits: bits12[0, 7] + bits12[8, 4]) return (n * 25) - 1000 end nil end |
.parse_line(opts = {}) ⇒ Object
- Supported Method Parameters
PWN::SDR::Decoder::ADSB.parse_line(line: 'MSG,3,1,1,ABCDEF,...')
228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 |
# File 'lib/pwn/sdr/decoder/adsb.rb', line 228 public_class_method def self.parse_line(opts = {}) line = opts[:line].to_s return { protocol: 'ADSB-UAT', summary: line.strip } unless line.start_with?('MSG,') f = line.split(',', -1) out = { protocol: 'ADSB' } SBS_FIELDS.each_with_index { |k, i| out[k] = f[i] unless f[i].to_s.empty? } bits = [] bits << "ICAO=#{out[:icao24]}" if out[:icao24] bits << "CS=#{out[:callsign].to_s.strip}" if out[:callsign] bits << "ALT=#{out[:altitude_ft]}ft" if out[:altitude_ft] bits << "POS=#{out[:lat]},#{out[:lon]}" if out[:lat] && out[:lon] bits << "GS=#{out[:ground_speed_kt]}kt" if out[:ground_speed_kt] bits << "SQK=#{out[:squawk]}" if out[:squawk] out[:summary] = "ADSB #{bits.join(' ')}".strip out end |