rtlsdr 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/lib/rtlsdr/device.rb CHANGED
@@ -43,8 +43,22 @@ module RTLSDR
43
43
  class Device
44
44
  include Enumerable
45
45
 
46
- attr_reader :index, :handle
47
-
46
+ # @return [Integer] Device index (0-based)
47
+ attr_reader :index
48
+ # @return [FFI::Pointer] Internal device handle pointer
49
+ attr_reader :handle
50
+
51
+ # Create a new RTL-SDR device instance
52
+ #
53
+ # Opens the specified RTL-SDR device and prepares it for use. The device
54
+ # will be automatically opened during initialization.
55
+ #
56
+ # @param [Integer] index Device index to open (default: 0)
57
+ # @raise [DeviceNotFoundError] if device doesn't exist
58
+ # @raise [DeviceOpenError] if device cannot be opened
59
+ # @example Create device instance
60
+ # device = RTLSDR::Device.new(0)
61
+ # puts "Opened: #{device.name}"
48
62
  def initialize(index = 0)
49
63
  @index = index
50
64
  @handle = nil
@@ -59,6 +73,16 @@ module RTLSDR
59
73
  !@handle.nil?
60
74
  end
61
75
 
76
+ # Close the RTL-SDR device
77
+ #
78
+ # Closes the device handle and releases system resources. If async reading
79
+ # is active, it will be cancelled first. After closing, the device cannot
80
+ # be used until reopened.
81
+ #
82
+ # @return [void]
83
+ # @example Close device
84
+ # device.close
85
+ # puts "Device closed"
62
86
  def close
63
87
  return unless open?
64
88
 
@@ -68,6 +92,9 @@ module RTLSDR
68
92
  check_result(result, "Failed to close device")
69
93
  end
70
94
 
95
+ # Check if the device is closed
96
+ #
97
+ # @return [Boolean] true if device is closed, false if open
71
98
  def closed?
72
99
  !open?
73
100
  end
@@ -77,6 +104,15 @@ module RTLSDR
77
104
  RTLSDR.device_name(@index)
78
105
  end
79
106
 
107
+ # Get USB device strings
108
+ #
109
+ # Retrieves the USB manufacturer, product, and serial number strings
110
+ # for this device. Results are cached after the first call.
111
+ #
112
+ # @return [Hash] Hash with :manufacturer, :product, :serial keys
113
+ # @example Get USB information
114
+ # usb_info = device.usb_strings
115
+ # puts "#{usb_info[:manufacturer]} #{usb_info[:product]}"
80
116
  def usb_strings
81
117
  return @usb_strings if @usb_strings
82
118
 
@@ -94,10 +130,22 @@ module RTLSDR
94
130
  }
95
131
  end
96
132
 
133
+ # Get the tuner type constant
134
+ #
135
+ # Returns the tuner type as one of the RTLSDR_TUNER_* constants.
136
+ # The result is cached after the first call.
137
+ #
138
+ # @return [Integer] Tuner type constant
139
+ # @see RTLSDR::FFI::RTLSDR_TUNER_*
97
140
  def tuner_type
98
141
  @tuner_type ||= FFI.rtlsdr_get_tuner_type(@handle)
99
142
  end
100
143
 
144
+ # Get human-readable tuner name
145
+ #
146
+ # @return [String] Tuner chip name and manufacturer
147
+ # @example Get tuner information
148
+ # puts "Tuner: #{device.tuner_name}"
101
149
  def tuner_name
102
150
  FFI.tuner_type_name(tuner_type)
103
151
  end
@@ -108,17 +156,33 @@ module RTLSDR
108
156
  check_result(result, "Failed to set center frequency")
109
157
  end
110
158
 
159
+ # Get the current center frequency
160
+ #
161
+ # @return [Integer] Center frequency in Hz
111
162
  def center_freq
112
163
  FFI.rtlsdr_get_center_freq(@handle)
113
164
  end
165
+ # @!method frequency
166
+ # Alias for {#center_freq}
167
+ # @return [Integer] Center frequency in Hz
114
168
  alias frequency center_freq
169
+ # @!method frequency=
170
+ # Alias for {#center_freq=}
115
171
  alias frequency= center_freq=
116
172
 
173
+ # Set frequency correction in PPM
174
+ #
175
+ # @param [Integer] ppm Frequency correction in parts per million
176
+ # @example Set 15 PPM correction
177
+ # device.freq_correction = 15
117
178
  def freq_correction=(ppm)
118
179
  result = FFI.rtlsdr_set_freq_correction(@handle, ppm)
119
180
  check_result(result, "Failed to set frequency correction")
120
181
  end
121
182
 
183
+ # Get current frequency correction
184
+ #
185
+ # @return [Integer] Frequency correction in PPM
122
186
  def freq_correction
123
187
  FFI.rtlsdr_get_freq_correction(@handle)
124
188
  end
@@ -130,6 +194,9 @@ module RTLSDR
130
194
  [rtl_freq, tuner_freq]
131
195
  end
132
196
 
197
+ # Get crystal oscillator frequencies
198
+ #
199
+ # @return [Array<Integer>] Array of [rtl_freq, tuner_freq] in Hz
133
200
  def xtal_freq
134
201
  rtl_freq_ptr = ::FFI::MemoryPointer.new(:uint32)
135
202
  tuner_freq_ptr = ::FFI::MemoryPointer.new(:uint32)
@@ -154,37 +221,67 @@ module RTLSDR
154
221
  gains_ptr.read_array_of_int(result)
155
222
  end
156
223
 
224
+ # Set tuner gain in tenths of dB
225
+ #
226
+ # @param [Integer] gain Gain in tenths of dB (e.g., 496 = 49.6 dB)
227
+ # @example Set 40 dB gain
228
+ # device.tuner_gain = 400
157
229
  def tuner_gain=(gain)
158
230
  result = FFI.rtlsdr_set_tuner_gain(@handle, gain)
159
231
  check_result(result, "Failed to set tuner gain")
160
232
  end
161
233
 
234
+ # Get current tuner gain
235
+ #
236
+ # @return [Integer] Current gain in tenths of dB
162
237
  def tuner_gain
163
238
  FFI.rtlsdr_get_tuner_gain(@handle)
164
239
  end
240
+ # @!method gain
241
+ # Alias for {#tuner_gain}
242
+ # @return [Integer] Current gain in tenths of dB
165
243
  alias gain tuner_gain
244
+ # @!method gain=
245
+ # Alias for {#tuner_gain=}
166
246
  alias gain= tuner_gain=
167
247
 
248
+ # Set gain mode (manual or automatic)
249
+ #
250
+ # @param [Boolean] manual true for manual gain mode, false for automatic
168
251
  def tuner_gain_mode=(manual)
169
252
  mode = manual ? 1 : 0
170
253
  result = FFI.rtlsdr_set_tuner_gain_mode(@handle, mode)
171
254
  check_result(result, "Failed to set gain mode")
172
255
  end
173
256
 
257
+ # Enable manual gain mode
258
+ #
259
+ # @return [Boolean] true
174
260
  def manual_gain_mode!
175
261
  self.tuner_gain_mode = true
176
262
  end
177
263
 
264
+ # Enable automatic gain mode
265
+ #
266
+ # @return [Boolean] false
178
267
  def auto_gain_mode!
179
268
  self.tuner_gain_mode = false
180
269
  end
181
270
 
271
+ # Set IF gain for specific stage
272
+ #
273
+ # @param [Integer] stage IF stage number
274
+ # @param [Integer] gain Gain value in tenths of dB
275
+ # @return [Integer] The gain value that was set
182
276
  def set_tuner_if_gain(stage, gain)
183
277
  result = FFI.rtlsdr_set_tuner_if_gain(@handle, stage, gain)
184
278
  check_result(result, "Failed to set IF gain")
185
279
  gain
186
280
  end
187
281
 
282
+ # Set tuner bandwidth
283
+ #
284
+ # @param [Integer] bw Bandwidth in Hz
188
285
  def tuner_bandwidth=(bw)
189
286
  result = FFI.rtlsdr_set_tuner_bandwidth(@handle, bw)
190
287
  check_result(result, "Failed to set bandwidth")
@@ -196,10 +293,18 @@ module RTLSDR
196
293
  check_result(result, "Failed to set sample rate")
197
294
  end
198
295
 
296
+ # Get current sample rate
297
+ #
298
+ # @return [Integer] Sample rate in Hz
199
299
  def sample_rate
200
300
  FFI.rtlsdr_get_sample_rate(@handle)
201
301
  end
302
+ # @!method samp_rate
303
+ # Alias for {#sample_rate}
304
+ # @return [Integer] Sample rate in Hz
202
305
  alias samp_rate sample_rate
306
+ # @!method samp_rate=
307
+ # Alias for {#sample_rate=}
203
308
  alias samp_rate= sample_rate=
204
309
 
205
310
  # Mode control
@@ -209,25 +314,40 @@ module RTLSDR
209
314
  check_result(result, "Failed to set test mode")
210
315
  end
211
316
 
317
+ # Enable test mode
318
+ #
319
+ # @return [Boolean] true
212
320
  def test_mode!
213
321
  self.test_mode = true
214
322
  end
215
323
 
324
+ # Set automatic gain control mode
325
+ #
326
+ # @param [Boolean] enabled true to enable AGC, false to disable
216
327
  def agc_mode=(enabled)
217
328
  mode = enabled ? 1 : 0
218
329
  result = FFI.rtlsdr_set_agc_mode(@handle, mode)
219
330
  check_result(result, "Failed to set AGC mode")
220
331
  end
221
332
 
333
+ # Enable automatic gain control
334
+ #
335
+ # @return [Boolean] true
222
336
  def agc_mode!
223
337
  self.agc_mode = true
224
338
  end
225
339
 
340
+ # Set direct sampling mode
341
+ #
342
+ # @param [Integer] mode Direct sampling mode (0=off, 1=I-ADC, 2=Q-ADC)
226
343
  def direct_sampling=(mode)
227
344
  result = FFI.rtlsdr_set_direct_sampling(@handle, mode)
228
345
  check_result(result, "Failed to set direct sampling")
229
346
  end
230
347
 
348
+ # Get current direct sampling mode
349
+ #
350
+ # @return [Integer, nil] Direct sampling mode or nil on error
231
351
  def direct_sampling
232
352
  result = FFI.rtlsdr_get_direct_sampling(@handle)
233
353
  return nil if result.negative?
@@ -235,12 +355,18 @@ module RTLSDR
235
355
  result
236
356
  end
237
357
 
358
+ # Set offset tuning mode
359
+ #
360
+ # @param [Boolean] enabled true to enable offset tuning, false to disable
238
361
  def offset_tuning=(enabled)
239
362
  mode = enabled ? 1 : 0
240
363
  result = FFI.rtlsdr_set_offset_tuning(@handle, mode)
241
364
  check_result(result, "Failed to set offset tuning")
242
365
  end
243
366
 
367
+ # Get current offset tuning mode
368
+ #
369
+ # @return [Boolean, nil] true if enabled, false if disabled, nil on error
244
370
  def offset_tuning
245
371
  result = FFI.rtlsdr_get_offset_tuning(@handle)
246
372
  return nil if result.negative?
@@ -248,6 +374,9 @@ module RTLSDR
248
374
  result == 1
249
375
  end
250
376
 
377
+ # Enable offset tuning
378
+ #
379
+ # @return [Boolean] true
251
380
  def offset_tuning!
252
381
  self.offset_tuning = true
253
382
  end
@@ -259,10 +388,18 @@ module RTLSDR
259
388
  check_result(result, "Failed to set bias tee")
260
389
  end
261
390
 
391
+ # Enable bias tee
392
+ #
393
+ # @return [Boolean] true
262
394
  def bias_tee!
263
395
  self.bias_tee = true
264
396
  end
265
397
 
398
+ # Set bias tee GPIO state
399
+ #
400
+ # @param [Integer] gpio GPIO pin number
401
+ # @param [Boolean] enabled true to enable, false to disable
402
+ # @return [Boolean] The enabled state that was set
266
403
  def set_bias_tee_gpio(gpio, enabled)
267
404
  mode = enabled ? 1 : 0
268
405
  result = FFI.rtlsdr_set_bias_tee_gpio(@handle, gpio, mode)
@@ -278,6 +415,11 @@ module RTLSDR
278
415
  data_ptr.read_array_of_uint8(length)
279
416
  end
280
417
 
418
+ # Write data to EEPROM
419
+ #
420
+ # @param [Array<Integer>] data Array of bytes to write
421
+ # @param [Integer] offset EEPROM offset address
422
+ # @return [Integer] Number of bytes written
281
423
  def write_eeprom(data, offset)
282
424
  data_ptr = ::FFI::MemoryPointer.new(:uint8, data.length)
283
425
  data_ptr.write_array_of_uint8(data)
@@ -292,6 +434,13 @@ module RTLSDR
292
434
  check_result(result, "Failed to reset buffer")
293
435
  end
294
436
 
437
+ # Read raw IQ data synchronously
438
+ #
439
+ # Reads raw 8-bit IQ data from the device. The buffer is automatically
440
+ # reset on the first read to avoid stale data.
441
+ #
442
+ # @param [Integer] length Number of bytes to read
443
+ # @return [Array<Integer>] Array of 8-bit unsigned integers
295
444
  def read_sync(length)
296
445
  # Reset buffer before first read to avoid stale data
297
446
  reset_buffer unless @buffer_reset_done
@@ -307,6 +456,16 @@ module RTLSDR
307
456
  buffer.read_array_of_uint8(n_read)
308
457
  end
309
458
 
459
+ # Read complex samples synchronously
460
+ #
461
+ # Reads the specified number of complex samples from the device and
462
+ # converts them from raw 8-bit IQ data to Ruby Complex numbers.
463
+ #
464
+ # @param [Integer] count Number of complex samples to read (default: 1024)
465
+ # @return [Array<Complex>] Array of complex samples
466
+ # @example Read 2048 samples
467
+ # samples = device.read_samples(2048)
468
+ # puts "Read #{samples.length} samples"
310
469
  def read_samples(count = 1024)
311
470
  # RTL-SDR outputs 8-bit I/Q samples, so we need 2 bytes per complex sample
312
471
  data = read_sync(count * 2)
@@ -322,10 +481,24 @@ module RTLSDR
322
481
  samples
323
482
  end
324
483
 
484
+ # Check if asynchronous streaming is active
485
+ #
486
+ # @return [Boolean] true if streaming, false otherwise
325
487
  def streaming?
326
488
  @streaming
327
489
  end
328
490
 
491
+ # Read raw IQ data asynchronously
492
+ #
493
+ # Starts asynchronous reading of raw 8-bit IQ data. The provided block
494
+ # will be called for each buffer of data received.
495
+ #
496
+ # @param [Integer] buffer_count Number of buffers to use (default: 15)
497
+ # @param [Integer] buffer_length Length of each buffer in bytes (default: 262144)
498
+ # @yield [Array<Integer>] Block called with each buffer of raw IQ data
499
+ # @return [Thread] Thread object running the async operation
500
+ # @raise [ArgumentError] if no block is provided
501
+ # @raise [OperationFailedError] if already streaming
329
502
  def read_async(buffer_count: 15, buffer_length: 262_144, &block)
330
503
  raise ArgumentError, "Block required for async reading" unless block_given?
331
504
  raise OperationFailedError, "Already streaming" if streaming?
@@ -349,6 +522,16 @@ module RTLSDR
349
522
  @async_thread
350
523
  end
351
524
 
525
+ # Read complex samples asynchronously
526
+ #
527
+ # Starts asynchronous reading and converts raw IQ data to complex samples.
528
+ # The provided block will be called for each buffer of complex samples.
529
+ #
530
+ # @param [Integer] buffer_count Number of buffers to use (default: 15)
531
+ # @param [Integer] buffer_length Length of each buffer in bytes (default: 262144)
532
+ # @yield [Array<Complex>] Block called with each buffer of complex samples
533
+ # @return [Thread] Thread object running the async operation
534
+ # @raise [ArgumentError] if no block is provided
352
535
  def read_samples_async(buffer_count: 15, buffer_length: 262_144, &block)
353
536
  raise ArgumentError, "Block required for async reading" unless block_given?
354
537
 
@@ -365,6 +548,12 @@ module RTLSDR
365
548
  end
366
549
  end
367
550
 
551
+ # Cancel asynchronous reading operation
552
+ #
553
+ # Stops any active asynchronous reading and cleans up resources.
554
+ # This method is safe to call from within async callbacks.
555
+ #
556
+ # @return [void]
368
557
  def cancel_async
369
558
  return unless streaming?
370
559
 
@@ -439,6 +628,9 @@ module RTLSDR
439
628
  }
440
629
  end
441
630
 
631
+ # Return string representation of device
632
+ #
633
+ # @return [String] Human-readable device information
442
634
  def inspect
443
635
  if open?
444
636
  "#<RTLSDR::Device:#{object_id.to_s(16)} index=#{@index} name=\"#{name}\" tuner=\"#{tuner_name}\" freq=#{center_freq}Hz rate=#{sample_rate}Hz>" # rubocop:disable Layout/LineLength
@@ -449,6 +641,12 @@ module RTLSDR
449
641
 
450
642
  private
451
643
 
644
+ # Open the RTL-SDR device handle
645
+ #
646
+ # @return [void]
647
+ # @raise [DeviceNotFoundError] if device doesn't exist
648
+ # @raise [DeviceOpenError] if device cannot be opened
649
+ # @private
452
650
  def open_device
453
651
  dev_ptr = ::FFI::MemoryPointer.new(:pointer)
454
652
  result = FFI.rtlsdr_open(dev_ptr, @index)
@@ -467,6 +665,13 @@ module RTLSDR
467
665
  end
468
666
  end
469
667
 
668
+ # Check FFI function result and raise appropriate error
669
+ #
670
+ # @param [Integer] result Return code from FFI function
671
+ # @param [String] message Error message prefix
672
+ # @return [void]
673
+ # @raise [DeviceNotOpenError, InvalidArgumentError, EEPROMError, OperationFailedError]
674
+ # @private
470
675
  def check_result(result, message)
471
676
  return if result.zero?
472
677
 
data/lib/rtlsdr/dsp.rb CHANGED
@@ -33,6 +33,17 @@ module RTLSDR
33
33
  # phases = RTLSDR::DSP.phase(filtered)
34
34
  module DSP
35
35
  # Convert raw IQ data to complex samples
36
+ #
37
+ # Converts raw 8-bit IQ data from RTL-SDR devices to Ruby Complex numbers.
38
+ # The RTL-SDR outputs unsigned 8-bit integers centered at 128, which are
39
+ # converted to floating point values in the range [-1.0, 1.0].
40
+ #
41
+ # @param [Array<Integer>] data Array of 8-bit unsigned integers (I, Q, I, Q, ...)
42
+ # @return [Array<Complex>] Array of Complex numbers representing I+jQ samples
43
+ # @example Convert device samples
44
+ # raw_data = [127, 130, 125, 135, 120, 140] # 3 IQ pairs
45
+ # samples = RTLSDR::DSP.iq_to_complex(raw_data)
46
+ # # => [(-0.008+0.016i), (-0.024+0.055i), (-0.063+0.094i)]
36
47
  def self.iq_to_complex(data)
37
48
  samples = []
38
49
  (0...data.length).step(2) do |i|
@@ -44,6 +55,18 @@ module RTLSDR
44
55
  end
45
56
 
46
57
  # Calculate power spectral density
58
+ #
59
+ # Computes a basic power spectrum from complex samples using windowing.
60
+ # This applies a Hanning window to reduce spectral leakage and then
61
+ # calculates the power (magnitude squared) for each sample. A proper
62
+ # FFT implementation would require an external library.
63
+ #
64
+ # @param [Array<Complex>] samples Array of complex samples
65
+ # @param [Integer] window_size Size of the analysis window (default 1024)
66
+ # @return [Array<Float>] Power spectrum values
67
+ # @example Calculate spectrum
68
+ # spectrum = RTLSDR::DSP.power_spectrum(samples, 512)
69
+ # max_power = spectrum.max
47
70
  def self.power_spectrum(samples, window_size = 1024)
48
71
  return [] if samples.length < window_size
49
72
 
@@ -59,7 +82,16 @@ module RTLSDR
59
82
  windowed_samples.map { |s| ((s.real**2) + (s.imag**2)) }
60
83
  end
61
84
 
62
- # Calculate average power
85
+ # Calculate average power of complex samples
86
+ #
87
+ # Computes the mean power (magnitude squared) across all samples.
88
+ # This is useful for signal strength measurements and AGC calculations.
89
+ #
90
+ # @param [Array<Complex>] samples Array of complex samples
91
+ # @return [Float] Average power value (0.0 if no samples)
92
+ # @example Measure signal power
93
+ # power = RTLSDR::DSP.average_power(samples)
94
+ # power_db = 10 * Math.log10(power + 1e-10)
63
95
  def self.average_power(samples)
64
96
  return 0.0 if samples.empty?
65
97
 
@@ -67,7 +99,17 @@ module RTLSDR
67
99
  total_power / samples.length
68
100
  end
69
101
 
70
- # Find peak power and frequency bin
102
+ # Find peak power and frequency bin in spectrum
103
+ #
104
+ # Locates the frequency bin with maximum power in a power spectrum.
105
+ # Returns both the bin index and the power value at that bin.
106
+ #
107
+ # @param [Array<Float>] power_spectrum Array of power values
108
+ # @return [Array<Integer, Float>] [bin_index, peak_power] or [0, 0.0] if empty
109
+ # @example Find strongest signal
110
+ # spectrum = RTLSDR::DSP.power_spectrum(samples)
111
+ # peak_bin, peak_power = RTLSDR::DSP.find_peak(spectrum)
112
+ # freq_offset = (peak_bin - spectrum.length/2) * sample_rate / spectrum.length
71
113
  def self.find_peak(power_spectrum)
72
114
  return [0, 0.0] if power_spectrum.empty?
73
115
 
@@ -76,7 +118,17 @@ module RTLSDR
76
118
  [max_index, max_power]
77
119
  end
78
120
 
79
- # DC removal (high-pass filter)
121
+ # Remove DC component using high-pass filter
122
+ #
123
+ # Applies a simple first-order high-pass filter to remove DC bias
124
+ # from the signal. This is useful for RTL-SDR devices which often
125
+ # have a DC offset in their I/Q samples.
126
+ #
127
+ # @param [Array<Complex>] samples Array of complex samples
128
+ # @param [Float] alpha Filter coefficient (0.995 = ~160Hz cutoff at 2.4MHz sample rate)
129
+ # @return [Array<Complex>] Filtered samples with DC component removed
130
+ # @example Remove DC bias
131
+ # clean_samples = RTLSDR::DSP.remove_dc(samples, 0.99)
80
132
  def self.remove_dc(samples, alpha = 0.995)
81
133
  return samples if samples.empty?
82
134
 
@@ -87,17 +139,46 @@ module RTLSDR
87
139
  filtered
88
140
  end
89
141
 
90
- # Simple magnitude detection
142
+ # Extract magnitude from complex samples
143
+ #
144
+ # Calculates the magnitude (absolute value) of each complex sample.
145
+ # This converts I+jQ samples to their envelope/amplitude values.
146
+ #
147
+ # @param [Array<Complex>] samples Array of complex samples
148
+ # @return [Array<Float>] Array of magnitude values
149
+ # @example Get signal envelope
150
+ # magnitudes = RTLSDR::DSP.magnitude(samples)
151
+ # peak_amplitude = magnitudes.max
91
152
  def self.magnitude(samples)
92
153
  samples.map(&:abs)
93
154
  end
94
155
 
95
- # Phase detection
156
+ # Extract phase from complex samples
157
+ #
158
+ # Calculates the phase angle (argument) of each complex sample in radians.
159
+ # The phase represents the angle between the I and Q components.
160
+ #
161
+ # @param [Array<Complex>] samples Array of complex samples
162
+ # @return [Array<Float>] Array of phase values in radians (-π to π)
163
+ # @example Get phase information
164
+ # phases = RTLSDR::DSP.phase(samples)
165
+ # phase_degrees = phases.map { |p| p * 180 / Math::PI }
96
166
  def self.phase(samples)
97
167
  samples.map { |s| Math.atan2(s.imag, s.real) }
98
168
  end
99
169
 
100
- # Frequency estimation using zero crossings
170
+ # Estimate frequency using zero-crossing detection
171
+ #
172
+ # Provides a rough frequency estimate by counting zero crossings in the
173
+ # magnitude signal. This is a simple method that works reasonably well
174
+ # for single-tone signals but may be inaccurate for complex signals.
175
+ #
176
+ # @param [Array<Complex>] samples Array of complex samples
177
+ # @param [Integer] sample_rate Sample rate in Hz
178
+ # @return [Float] Estimated frequency in Hz
179
+ # @example Estimate carrier frequency
180
+ # freq_hz = RTLSDR::DSP.estimate_frequency(samples, 2_048_000)
181
+ # puts "Estimated frequency: #{freq_hz} Hz"
101
182
  def self.estimate_frequency(samples, sample_rate)
102
183
  return 0.0 if samples.length < 2
103
184
 
data/lib/rtlsdr/errors.rb CHANGED
@@ -1,12 +1,72 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RTLSDR
4
+ # Base error class for all RTL-SDR related exceptions
5
+ #
6
+ # This is the parent class for all RTL-SDR specific errors. It extends
7
+ # Ruby's StandardError and provides a consistent error hierarchy for
8
+ # the gem. All other RTL-SDR errors inherit from this class.
9
+ #
10
+ # @since 0.1.0
4
11
  class Error < StandardError; end
12
+
13
+ # Raised when a requested device cannot be found
14
+ #
15
+ # This exception is thrown when attempting to open or access a device
16
+ # by index that doesn't exist, or when a device with a specific serial
17
+ # number cannot be located.
18
+ #
19
+ # @since 0.1.0
5
20
  class DeviceNotFoundError < Error; end
21
+
22
+ # Raised when a device cannot be opened
23
+ #
24
+ # This exception occurs when a device exists but cannot be opened,
25
+ # typically because it's already in use by another process or the
26
+ # user lacks sufficient permissions.
27
+ #
28
+ # @since 0.1.0
6
29
  class DeviceOpenError < Error; end
30
+
31
+ # Raised when attempting to use a device that hasn't been opened
32
+ #
33
+ # This exception is thrown when trying to perform operations on a
34
+ # device that has been closed or was never properly opened.
35
+ #
36
+ # @since 0.1.0
7
37
  class DeviceNotOpenError < Error; end
38
+
39
+ # Raised when invalid arguments are passed to device functions
40
+ #
41
+ # This exception occurs when function parameters are out of range,
42
+ # of the wrong type, or otherwise invalid for the requested operation.
43
+ #
44
+ # @since 0.1.0
8
45
  class InvalidArgumentError < Error; end
46
+
47
+ # Raised when a device operation fails
48
+ #
49
+ # This is a general exception for operations that fail at the hardware
50
+ # or driver level, such as setting frequencies, gains, or sample rates
51
+ # that the device cannot support.
52
+ #
53
+ # @since 0.1.0
9
54
  class OperationFailedError < Error; end
55
+
56
+ # Raised when EEPROM operations fail
57
+ #
58
+ # This exception occurs when reading from or writing to the device's
59
+ # EEPROM memory fails, either due to hardware issues or invalid
60
+ # memory addresses.
61
+ #
62
+ # @since 0.1.0
10
63
  class EEPROMError < Error; end
64
+
65
+ # Raised when asynchronous callback operations fail
66
+ #
67
+ # This exception is thrown when errors occur within the async reading
68
+ # callback functions, typically during real-time sample processing.
69
+ #
70
+ # @since 0.1.0
11
71
  class CallbackError < Error; end
12
72
  end