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

.authorsObject

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

.helpObject

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