Module: PWN::SDR::Decoder::APT

Defined in:
lib/pwn/sdr/decoder/apt.rb

Overview

Pure-Ruby NOAA APT (Automatic Picture Transmission) decoder for the 137 MHz polar-orbiting weather satellites (NOAA-15/18/19).

APT is a 2400 Hz AM subcarrier inside a ~34 kHz-wide FM downlink carrying two 909-pixel image channels at 2 lines/second (2080 words/line). This module envelope-demodulates the 2400 Hz carrier from audio, resamples to 4160 words/sec with continuous phase, and aligns the initial line on a Sync-A correlation candidate. Fixed-rate rows update a bounded rolling P5 PGM; there is no drift tracking or satellite identification. Line contrast is normalized independently. No sox, no noaa-apt.

Defined Under Namespace

Classes: Demod

Constant Summary collapse

WORDS_PER_LINE =
2080
LINES_PER_SEC =
2
WORD_RATE =

4160 Hz

WORDS_PER_LINE * LINES_PER_SEC
SYNC_A =

Sync-A: 7 cycles of 1040 Hz square = 1 1 0 0 repeated 7 times

(Array.new(4, 0) + ([1, 1, 0, 0] * 7) + Array.new(7, 0)).freeze

Class Method Summary collapse

Class Method Details

.authorsObject

Author(s)

0day Inc. [email protected]



173
174
175
# File 'lib/pwn/sdr/decoder/apt.rb', line 173

public_class_method def self.authors
  "AUTHOR(S):\n  0day Inc. <[email protected]>\n"
end

.decode(opts = {}) ⇒ Object



149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
# File 'lib/pwn/sdr/decoder/apt.rb', line 149

public_class_method def self.decode(opts = {})
  freq_obj = opts[:freq_obj]
  raise 'ERROR: :freq_obj is required' unless freq_obj.is_a?(Hash)

  want_iq = opts[:source] || opts[:file] || freq_obj[:iq_source] || freq_obj[:iq_file]
  rate = if want_iq
           opts[:sample_rate] || freq_obj[:iq_rate] || 48_000
         else
           opts[:rate] || 48_000
         end
  demod = Demod.new(rate: rate, out_path: opts[:out_path], max_lines: opts.fetch(:max_lines, 1024))
  common = opts.merge(freq_obj: freq_obj, protocol: 'NOAA-APT', demod: demod)
  if want_iq
    PWN::SDR::Decoder::Base.run_iq(common.merge(
                                     sample_rate: rate.to_i, fm_demod: true, fallback: :raise,
                                     note: 'NOAA APT: FM-demod I/Q then 2400 Hz AM envelope → rolling PGM.'
                                   ))
  else
    PWN::SDR::Decoder::Base.run_native(common.merge(rate: rate.to_i))
  end
end

.detect(opts = {}) ⇒ Object

Energy detection only; does not identify or decode APT payloads.

Supported Method Parameters

APT.detect(freq_obj: Hash, threshold: 8.0, on_frame: Proc)



141
142
143
144
145
146
147
# File 'lib/pwn/sdr/decoder/apt.rb', line 141

public_class_method def self.detect(opts = {})
  Base.run_detector(opts.merge(
                      protocol: 'APT',
                      note: 'Energy detection only; no protocol payload decoding.',
                      describe: proc { |_burst| { event: 'detection', capability: 'energy-detection', decoded: false } }
                    ))
end

.helpObject

Display Usage for this Module



179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
# File 'lib/pwn/sdr/decoder/apt.rb', line 179

public_class_method def self.help
  puts "USAGE:
    # Detect energy only (not protocol payloads); accepts Base runner controls.
    #{self}.detect(freq_obj: {}, threshold: 8.0, on_frame: nil)
    # Run decode and return its result
    #{self}.decode(
      freq_obj: 'required - freq_obj returned from PWN::SDR::GQRX.init_freq',
      source: 'optional - source value consumed by #decode',
      file: 'optional - filesystem path',
      out_path: 'optional - out path value consumed by #decode',
      sample_rate: 'optional - I/Q audio rate Hz (default 48000 or freq_obj[:iq_rate])',
      rate: 'optional - native audio rate Hz (default 48000)',
      max_lines: 'optional - rolling image height limit (default 1024)',
      on_frame: 'optional - callback receiving each line Hash after atomic PGM update',
      output: 'optional - IO for immediate JSONL events (default stdout)',
      interactive: 'optional - stop on ENTER (default true)',
      duration: 'optional - maximum stream seconds',
      stop: 'optional - callable cancellation predicate',
      queue_size: 'optional - bounded Base input queue size',
      log_file: 'optional - JSONL log path or false to disable'
    )

    # Print the AUTHOR(S) string for this module.
    #{self}.authors
  "
  constants.sort
end