Class: RTLSDR::Scanner

Inherits:
Object
  • Object
show all
Defined in:
lib/rtlsdr/scanner.rb

Overview

Frequency scanning and spectrum analysis

The Scanner class provides high-level frequency scanning capabilities for RTL-SDR devices. It automates the process of sweeping across frequency ranges, collecting samples, and analyzing signal characteristics. This is particularly useful for spectrum analysis, signal hunting, and surveillance applications.

Features:

  • Configurable frequency range and step size
  • Adjustable dwell time per frequency
  • Synchronous and asynchronous scanning modes
  • Peak detection with power thresholds
  • Power sweep analysis
  • Real-time result callbacks
  • Thread-safe scanning control

Examples:

Basic frequency scan

scanner = RTLSDR::Scanner.new(
  device,
  start_freq: 88_000_000,    # 88 MHz
  end_freq: 108_000_000,     # 108 MHz
  step_size: 100_000,        # 100 kHz steps
  dwell_time: 0.1            # 100ms per frequency
)

scanner.scan do |result|
  puts "#{result[:frequency] / 1e6} MHz: #{result[:power]} dBm"
end

Find strong signals

peaks = scanner.find_peaks(threshold: -60)
peaks.each do |peak|
  puts "Strong signal at #{peak[:frequency] / 1e6} MHz"
end

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(device, start_freq:, end_freq:, step_size: 1_000_000, dwell_time: 0.1) ⇒ Scanner

Create a new frequency scanner

Examples:

Create FM band scanner

scanner = RTLSDR::Scanner.new(
  device,
  start_freq: 88_000_000,    # 88 MHz
  end_freq: 108_000_000,     # 108 MHz
  step_size: 200_000,        # 200 kHz
  dwell_time: 0.05           # 50ms per frequency
)

Parameters:

  • device (RTLSDR::Device)

    RTL-SDR device to use for scanning

  • start_freq (Integer)

    Starting frequency in Hz

  • end_freq (Integer)

    Ending frequency in Hz

  • step_size (Integer) (defaults to: 1_000_000)

    Frequency step size in Hz (default: 1 MHz)

  • dwell_time (Float) (defaults to: 0.1)

    Time to spend on each frequency in seconds (default: 0.1s)



66
67
68
69
70
71
72
73
# File 'lib/rtlsdr/scanner.rb', line 66

def initialize(device, start_freq:, end_freq:, step_size: 1_000_000, dwell_time: 0.1)
  @device = device
  @start_freq = start_freq
  @end_freq = end_freq
  @step_size = step_size
  @dwell_time = dwell_time
  @scanning = false
end

Instance Attribute Details

#deviceRTLSDR::Device (readonly)

Returns The RTL-SDR device being used for scanning.

Returns:



41
42
43
# File 'lib/rtlsdr/scanner.rb', line 41

def device
  @device
end

#dwell_timeFloat (readonly)

Returns Time to dwell on each frequency in seconds.

Returns:

  • (Float)

    Time to dwell on each frequency in seconds



49
50
51
# File 'lib/rtlsdr/scanner.rb', line 49

def dwell_time
  @dwell_time
end

#end_freqInteger (readonly)

Returns Ending frequency in Hz.

Returns:

  • (Integer)

    Ending frequency in Hz



45
46
47
# File 'lib/rtlsdr/scanner.rb', line 45

def end_freq
  @end_freq
end

#start_freqInteger (readonly)

Returns Starting frequency in Hz.

Returns:

  • (Integer)

    Starting frequency in Hz



43
44
45
# File 'lib/rtlsdr/scanner.rb', line 43

def start_freq
  @start_freq
end

#step_sizeInteger (readonly)

Returns Frequency step size in Hz.

Returns:

  • (Integer)

    Frequency step size in Hz



47
48
49
# File 'lib/rtlsdr/scanner.rb', line 47

def step_size
  @step_size
end

Instance Method Details

#configure(start_freq: nil, end_freq: nil, step_size: nil, dwell_time: nil) ⇒ Scanner

Update scan configuration parameters

Allows modification of scan parameters after the scanner has been created. Only non-nil parameters will be updated.

Examples:

Reconfigure scanner

scanner.configure(start_freq: 400_000_000, step_size: 25_000)

Parameters:

  • start_freq (Integer, nil) (defaults to: nil)

    New starting frequency in Hz

  • end_freq (Integer, nil) (defaults to: nil)

    New ending frequency in Hz

  • step_size (Integer, nil) (defaults to: nil)

    New step size in Hz

  • dwell_time (Float, nil) (defaults to: nil)

    New dwell time in seconds

Returns:

  • (Scanner)

    self for method chaining



244
245
246
247
248
249
250
# File 'lib/rtlsdr/scanner.rb', line 244

def configure(start_freq: nil, end_freq: nil, step_size: nil, dwell_time: nil)
  @start_freq = start_freq if start_freq
  @end_freq = end_freq if end_freq
  @step_size = step_size if step_size
  @dwell_time = dwell_time if dwell_time
  self
end

#find_peaks(threshold: -60,, samples_per_freq: 1024) ⇒ Array<Hash>

Find signal peaks above a power threshold

Scans the frequency range and returns all frequencies where the signal power exceeds the specified threshold. Results are sorted by power in descending order (strongest signals first).

Examples:

Find strong signals

peaks = scanner.find_peaks(threshold: -50, samples_per_freq: 4096)
peaks.each do |peak|
  puts "#{peak[:frequency]/1e6} MHz: #{peak[:power_db]} dB"
end

Parameters:

  • threshold (Float) (defaults to: -60,)

    Power threshold in dB (default: -60 dB)

  • samples_per_freq (Integer) (defaults to: 1024)

    Number of samples per frequency

Returns:

  • (Array<Hash>)

    Array of peak information hashes



173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
# File 'lib/rtlsdr/scanner.rb', line 173

def find_peaks(threshold: -60, samples_per_freq: 1024)
  peaks = []

  scan(samples_per_freq: samples_per_freq) do |result|
    power_db = 10 * Math.log10(result[:power] + 1e-10)
    if power_db > threshold
      peaks << {
        frequency: result[:frequency],
        power: result[:power],
        power_db: power_db,
        timestamp: result[:timestamp]
      }
    end
  end

  peaks.sort_by { |peak| -peak[:power] }
end

#frequenciesArray<Integer>

Get array of all frequencies to be scanned

Examples:

Get frequency list

freqs = scanner.frequencies
puts "Will scan #{freqs.length} frequencies"

Returns:

  • (Array<Integer>)

    Array of frequencies in Hz



81
82
83
# File 'lib/rtlsdr/scanner.rb', line 81

def frequencies
  (@start_freq..@end_freq).step(@step_size).to_a
end

#frequency_countInteger

Get total number of frequencies to be scanned

Returns:

  • (Integer)

    Number of frequency steps



88
89
90
# File 'lib/rtlsdr/scanner.rb', line 88

def frequency_count
  ((end_freq - start_freq) / step_size).to_i + 1
end

#inspectString

Return string representation of scanner

Returns:

  • (String)

    Human-readable scanner configuration



255
256
257
# File 'lib/rtlsdr/scanner.rb', line 255

def inspect
  "#<RTLSDR::Scanner #{@start_freq / 1e6}MHz-#{@end_freq / 1e6}MHz step=#{@step_size / 1e6}MHz dwell=#{@dwell_time}s>" # rubocop:disable Layout/LineLength
end

#power_sweep(samples_per_freq: 1024) ⇒ Array<Array>

Perform a power sweep across the frequency range

Scans all frequencies and returns an array of [frequency, power_db] pairs. This is useful for generating spectrum plots or finding the overall power distribution across a frequency band.

Examples:

Generate spectrum data

spectrum_data = scanner.power_sweep(samples_per_freq: 2048)
spectrum_data.each do |freq, power|
  puts "#{freq/1e6} MHz: #{power.round(1)} dB"
end

Parameters:

  • samples_per_freq (Integer) (defaults to: 1024)

    Number of samples per frequency

Returns:

  • (Array<Array>)

    Array of [frequency_hz, power_db] pairs



204
205
206
207
208
209
210
211
212
213
# File 'lib/rtlsdr/scanner.rb', line 204

def power_sweep(samples_per_freq: 1024)
  results = []

  scan(samples_per_freq: samples_per_freq) do |result|
    power_db = 10 * Math.log10(result[:power] + 1e-10)
    results << [result[:frequency], power_db]
  end

  results
end

#scan(samples_per_freq: 1024) {|Hash| ... } ⇒ Hash

Perform a frequency sweep scan

Scans through all frequencies in the configured range, collecting samples and calling the provided block for each frequency. The block receives a hash with frequency, power, samples, and timestamp information.

Examples:

Scan and log results

results = scanner.scan(samples_per_freq: 2048) do |result|
  power_db = 10 * Math.log10(result[:power] + 1e-10)
  puts "#{result[:frequency]/1e6} MHz: #{power_db.round(1)} dB"
end

Parameters:

  • samples_per_freq (Integer) (defaults to: 1024)

    Number of samples to collect per frequency

Yields:

  • (Hash)

    Block called for each frequency with scan results

Yield Parameters:

  • result (Hash)

    Scan result containing :frequency, :power, :samples, :timestamp

Returns:

  • (Hash)

    Hash of all results keyed by frequency

Raises:

  • (ArgumentError)


107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
# File 'lib/rtlsdr/scanner.rb', line 107

def scan(samples_per_freq: 1024, &block)
  raise ArgumentError, "Block required for scan" unless block_given?

  @scanning = true
  results = {}

  frequencies.each do |freq|
    break unless @scanning

    @device.center_freq = freq
    sleep(@dwell_time)

    samples = @device.read_samples(samples_per_freq)
    power = DSP.average_power(samples)

    result = {
      frequency: freq,
      power: power,
      samples: samples,
      timestamp: Time.now
    }

    results[freq] = result
    block.call(result)
  end

  @scanning = false
  results
end

#scan_async(samples_per_freq: 1024) {|Hash| ... } ⇒ Thread

Perform asynchronous frequency sweep scan

Same as #scan but runs in a separate thread, allowing the calling thread to continue execution while scanning proceeds in the background.

Examples:

Background scanning

scan_thread = scanner.scan_async do |result|
  # Process results in background
end
# Do other work...
scan_thread.join  # Wait for completion

Parameters:

  • samples_per_freq (Integer) (defaults to: 1024)

    Number of samples to collect per frequency

Yields:

  • (Hash)

    Block called for each frequency with scan results

Returns:

  • (Thread)

    Thread object running the scan

Raises:

  • (ArgumentError)


151
152
153
154
155
156
157
# File 'lib/rtlsdr/scanner.rb', line 151

def scan_async(samples_per_freq: 1024, &block)
  raise ArgumentError, "Block required for async scan" unless block_given?

  Thread.new do
    scan(samples_per_freq: samples_per_freq, &block)
  end
end

#scanning?Boolean

Check if a scan is currently in progress

Returns:

  • (Boolean)

    true if scanning, false otherwise



228
229
230
# File 'lib/rtlsdr/scanner.rb', line 228

def scanning?
  @scanning
end

#stopBoolean

Stop the current scan operation

Sets the scanning flag to false, which will cause any active scan to terminate after the current frequency step completes.

Returns:

  • (Boolean)

    false (new scanning state)



221
222
223
# File 'lib/rtlsdr/scanner.rb', line 221

def stop
  @scanning = false
end