Module: PWN::SDR::Decoder::RTTY

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

Overview

Pure-Ruby RTTY (Radioteletype, ITA2/Baudot) decoder.

Amateur RTTY is 45.45 baud 2-FSK with a 170 Hz shift; on USB the convention is mark ≈ 2125 Hz, space ≈ 2295 Hz in the demodulated audio. This module runs a per-symbol Goertzel on both tones, frames 1-start / 5-data / 1.5-stop asynchronously, and decodes ITA2 via DSP::BAUDOT_LTRS/FIGS. No minimodem, no sox.

Defined Under Namespace

Classes: Demod

Class Method Summary collapse

Class Method Details

.authors ⇒ Object

Author(s)

0day Inc. [email protected]



155
156
157
# File 'lib/pwn/sdr/decoder/rtty.rb', line 155

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

.decode(opts = {}) ⇒ Object



124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
# File 'lib/pwn/sdr/decoder/rtty.rb', line 124

public_class_method def self.decode(opts = {})
  freq_obj = opts[:freq_obj]
  # Prefer true-air I/Q (FM-demod → existing audio demod) when the
  # operator asks for a source/file or sets freq_obj[:iq_source].
  # Otherwise keep the GQRX 48 kHz UDP audio path (run_native).
  want_iq = opts[:source] || opts[:file] || freq_obj[:iq_source] || freq_obj[:iq_file]
  if want_iq
    PWN::SDR::Decoder::Base.run_iq(
      **opts,
      fallback: :raise,
      freq_obj: freq_obj,
      protocol: 'RTTY',
      demod: Demod.new(rate: (opts[:sample_rate] || freq_obj[:iq_rate] || 48_000).to_i),
      sample_rate: (opts[:sample_rate] || freq_obj[:iq_rate] || 48_000).to_i,
      source: opts[:source],
      file: opts[:file],
      fm_demod: true,
      note: 'RTTY true-air: FM-demod I/Q then native bit recovery; missing I/Q raises (use .detect for energy only).'
    )
  else
    PWN::SDR::Decoder::Base.run_native(
      **opts,
      freq_obj: freq_obj,
      protocol: 'RTTY',
      demod: Demod.new(rate: (opts[:rate] || 48_000).to_i)
    )
  end
end

.detect(opts = {}) ⇒ Object

Realtime options forwarded to Base: on_frame (Hash callback), output (writable IO), interactive (default true), duration (seconds), stop (callable), queue_size (bounded chunks), log_file (path or false). Energy detection only; does not identify or decode RTTY payloads.

Supported Method Parameters

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



116
117
118
119
120
121
122
# File 'lib/pwn/sdr/decoder/rtty.rb', line 116

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

.help ⇒ Object

Display Usage for this Module



161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
# File 'lib/pwn/sdr/decoder/rtty.rb', line 161

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',
      on_frame: 'optional - callback receiving each emitted Hash',
      output: 'optional - writable IO (default stdout)',
      interactive: 'optional - false disables ENTER input',
      duration: 'optional - finite seconds to run',
      stop: 'optional - callable returning true to stop',
      queue_size: 'optional - bounded pending chunks (default 8)',
      log_file: 'optional - JSONL path or false to disable logging',
      source: 'optional - source value consumed by #decode',
      file: 'optional - filesystem path',
      sample_rate: 'optional - sample rate value consumed by #decode'
    )

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