rtlsdr 0.1.0 → 0.1.2

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/ffi.rb CHANGED
@@ -30,6 +30,7 @@ module RTLSDR
30
30
  #
31
31
  # @note Most users should use the high-level RTLSDR::Device class instead
32
32
  # of calling these FFI functions directly.
33
+ # @since 0.1.0
33
34
  module FFI
34
35
  extend ::FFI::Library
35
36
 
@@ -56,26 +57,54 @@ module RTLSDR
56
57
  # Opaque device pointer
57
58
  typedef :pointer, :rtlsdr_dev_t
58
59
 
59
- # Tuner types enum
60
+ # Tuner types enum constants
61
+ #
62
+ # These constants identify the different tuner chips that can be found
63
+ # in RTL-SDR devices. Each tuner has different characteristics and
64
+ # supported frequency ranges.
65
+
66
+ # Unknown or unsupported tuner type
60
67
  RTLSDR_TUNER_UNKNOWN = 0
68
+ # Elonics E4000 tuner (52 - 2200 MHz with gaps)
61
69
  RTLSDR_TUNER_E4000 = 1
70
+ # Fitipower FC0012 tuner (22 - 948 MHz)
62
71
  RTLSDR_TUNER_FC0012 = 2
72
+ # Fitipower FC0013 tuner (22 - 1100 MHz)
63
73
  RTLSDR_TUNER_FC0013 = 3
74
+ # FCI FC2580 tuner (146 - 308 MHz and 438 - 924 MHz)
64
75
  RTLSDR_TUNER_FC2580 = 4
76
+ # Rafael Micro R820T tuner (24 - 1766 MHz)
65
77
  RTLSDR_TUNER_R820T = 5
78
+ # Rafael Micro R828D tuner (24 - 1766 MHz)
66
79
  RTLSDR_TUNER_R828D = 6
67
80
 
68
- # Callback for async reading
81
+ # Callback function type for asynchronous reading
82
+ #
83
+ # This callback is called by the C library when new sample data is available
84
+ # during asynchronous reading operations. The callback receives a pointer to
85
+ # the buffer data, the length of the buffer, and a user context pointer.
69
86
  callback :rtlsdr_read_async_cb_t, %i[pointer uint32 pointer], :void
70
87
 
71
88
  # Device enumeration functions
89
+
90
+ # Get the number of available RTL-SDR devices
72
91
  attach_function :rtlsdr_get_device_count, [], :uint32
92
+
93
+ # Get the name of an RTL-SDR device by index
73
94
  attach_function :rtlsdr_get_device_name, [:uint32], :string
95
+
96
+ # Get USB device strings (manufacturer, product, serial)
74
97
  attach_function :rtlsdr_get_device_usb_strings, %i[uint32 pointer pointer pointer], :int
98
+
99
+ # Find device index by serial number
75
100
  attach_function :rtlsdr_get_index_by_serial, [:string], :int
76
101
 
77
102
  # Device control functions
103
+
104
+ # Open an RTL-SDR device
78
105
  attach_function :rtlsdr_open, %i[pointer uint32], :int
106
+
107
+ # Close an RTL-SDR device
79
108
  attach_function :rtlsdr_close, [:rtlsdr_dev_t], :int
80
109
 
81
110
  # Configuration functions
@@ -121,7 +150,13 @@ module RTLSDR
121
150
  attach_function :rtlsdr_set_bias_tee, %i[rtlsdr_dev_t int], :int
122
151
  attach_function :rtlsdr_set_bias_tee_gpio, %i[rtlsdr_dev_t int int], :int
123
152
 
124
- # Helper function to get tuner type name
153
+ # Convert tuner type constant to human-readable name
154
+ #
155
+ # @param [Integer] tuner_type One of the RTLSDR_TUNER_* constants
156
+ # @return [String] Human-readable tuner name with chip details
157
+ # @example Get tuner name
158
+ # RTLSDR::FFI.tuner_type_name(RTLSDR::FFI::RTLSDR_TUNER_R820T)
159
+ # # => "Rafael Micro R820T"
125
160
  def self.tuner_type_name(tuner_type)
126
161
  case tuner_type
127
162
  when RTLSDR_TUNER_UNKNOWN then "Unknown"
@@ -37,8 +37,32 @@ module RTLSDR
37
37
  # puts "Strong signal at #{peak[:frequency] / 1e6} MHz"
38
38
  # end
39
39
  class Scanner
40
- attr_reader :device, :start_freq, :end_freq, :step_size, :dwell_time
40
+ # @return [RTLSDR::Device] The RTL-SDR device being used for scanning
41
+ attr_reader :device
42
+ # @return [Integer] Starting frequency in Hz
43
+ attr_reader :start_freq
44
+ # @return [Integer] Ending frequency in Hz
45
+ attr_reader :end_freq
46
+ # @return [Integer] Frequency step size in Hz
47
+ attr_reader :step_size
48
+ # @return [Float] Time to dwell on each frequency in seconds
49
+ attr_reader :dwell_time
41
50
 
51
+ # Create a new frequency scanner
52
+ #
53
+ # @param [RTLSDR::Device] device RTL-SDR device to use for scanning
54
+ # @param [Integer] start_freq Starting frequency in Hz
55
+ # @param [Integer] end_freq Ending frequency in Hz
56
+ # @param [Integer] step_size Frequency step size in Hz (default: 1 MHz)
57
+ # @param [Float] dwell_time Time to spend on each frequency in seconds (default: 0.1s)
58
+ # @example Create FM band scanner
59
+ # scanner = RTLSDR::Scanner.new(
60
+ # device,
61
+ # start_freq: 88_000_000, # 88 MHz
62
+ # end_freq: 108_000_000, # 108 MHz
63
+ # step_size: 200_000, # 200 kHz
64
+ # dwell_time: 0.05 # 50ms per frequency
65
+ # )
42
66
  def initialize(device, start_freq:, end_freq:, step_size: 1_000_000, dwell_time: 0.1)
43
67
  @device = device
44
68
  @start_freq = start_freq
@@ -48,15 +72,38 @@ module RTLSDR
48
72
  @scanning = false
49
73
  end
50
74
 
75
+ # Get array of all frequencies to be scanned
76
+ #
77
+ # @return [Array<Integer>] Array of frequencies in Hz
78
+ # @example Get frequency list
79
+ # freqs = scanner.frequencies
80
+ # puts "Will scan #{freqs.length} frequencies"
51
81
  def frequencies
52
82
  (@start_freq..@end_freq).step(@step_size).to_a
53
83
  end
54
84
 
85
+ # Get total number of frequencies to be scanned
86
+ #
87
+ # @return [Integer] Number of frequency steps
55
88
  def frequency_count
56
89
  ((end_freq - start_freq) / step_size).to_i + 1
57
90
  end
58
91
 
59
- # Perform a sweep scan
92
+ # Perform a frequency sweep scan
93
+ #
94
+ # Scans through all frequencies in the configured range, collecting samples
95
+ # and calling the provided block for each frequency. The block receives a
96
+ # hash with frequency, power, samples, and timestamp information.
97
+ #
98
+ # @param [Integer] samples_per_freq Number of samples to collect per frequency
99
+ # @yield [Hash] Block called for each frequency with scan results
100
+ # @yieldparam result [Hash] Scan result containing :frequency, :power, :samples, :timestamp
101
+ # @return [Hash] Hash of all results keyed by frequency
102
+ # @example Scan and log results
103
+ # results = scanner.scan(samples_per_freq: 2048) do |result|
104
+ # power_db = 10 * Math.log10(result[:power] + 1e-10)
105
+ # puts "#{result[:frequency]/1e6} MHz: #{power_db.round(1)} dB"
106
+ # end
60
107
  def scan(samples_per_freq: 1024, &block)
61
108
  raise ArgumentError, "Block required for scan" unless block_given?
62
109
 
@@ -87,7 +134,20 @@ module RTLSDR
87
134
  results
88
135
  end
89
136
 
90
- # Async sweep scan
137
+ # Perform asynchronous frequency sweep scan
138
+ #
139
+ # Same as {#scan} but runs in a separate thread, allowing the calling
140
+ # thread to continue execution while scanning proceeds in the background.
141
+ #
142
+ # @param [Integer] samples_per_freq Number of samples to collect per frequency
143
+ # @yield [Hash] Block called for each frequency with scan results
144
+ # @return [Thread] Thread object running the scan
145
+ # @example Background scanning
146
+ # scan_thread = scanner.scan_async do |result|
147
+ # # Process results in background
148
+ # end
149
+ # # Do other work...
150
+ # scan_thread.join # Wait for completion
91
151
  def scan_async(samples_per_freq: 1024, &block)
92
152
  raise ArgumentError, "Block required for async scan" unless block_given?
93
153
 
@@ -96,7 +156,20 @@ module RTLSDR
96
156
  end
97
157
  end
98
158
 
99
- # Find peaks in the spectrum
159
+ # Find signal peaks above a power threshold
160
+ #
161
+ # Scans the frequency range and returns all frequencies where the signal
162
+ # power exceeds the specified threshold. Results are sorted by power
163
+ # in descending order (strongest signals first).
164
+ #
165
+ # @param [Float] threshold Power threshold in dB (default: -60 dB)
166
+ # @param [Integer] samples_per_freq Number of samples per frequency
167
+ # @return [Array<Hash>] Array of peak information hashes
168
+ # @example Find strong signals
169
+ # peaks = scanner.find_peaks(threshold: -50, samples_per_freq: 4096)
170
+ # peaks.each do |peak|
171
+ # puts "#{peak[:frequency]/1e6} MHz: #{peak[:power_db]} dB"
172
+ # end
100
173
  def find_peaks(threshold: -60, samples_per_freq: 1024)
101
174
  peaks = []
102
175
 
@@ -115,7 +188,19 @@ module RTLSDR
115
188
  peaks.sort_by { |peak| -peak[:power] }
116
189
  end
117
190
 
118
- # Power sweep - returns array of [frequency, power] pairs
191
+ # Perform a power sweep across the frequency range
192
+ #
193
+ # Scans all frequencies and returns an array of [frequency, power_db] pairs.
194
+ # This is useful for generating spectrum plots or finding the overall
195
+ # power distribution across a frequency band.
196
+ #
197
+ # @param [Integer] samples_per_freq Number of samples per frequency
198
+ # @return [Array<Array>] Array of [frequency_hz, power_db] pairs
199
+ # @example Generate spectrum data
200
+ # spectrum_data = scanner.power_sweep(samples_per_freq: 2048)
201
+ # spectrum_data.each do |freq, power|
202
+ # puts "#{freq/1e6} MHz: #{power.round(1)} dB"
203
+ # end
119
204
  def power_sweep(samples_per_freq: 1024)
120
205
  results = []
121
206
 
@@ -127,15 +212,35 @@ module RTLSDR
127
212
  results
128
213
  end
129
214
 
215
+ # Stop the current scan operation
216
+ #
217
+ # Sets the scanning flag to false, which will cause any active scan
218
+ # to terminate after the current frequency step completes.
219
+ #
220
+ # @return [Boolean] false (new scanning state)
130
221
  def stop
131
222
  @scanning = false
132
223
  end
133
224
 
225
+ # Check if a scan is currently in progress
226
+ #
227
+ # @return [Boolean] true if scanning, false otherwise
134
228
  def scanning?
135
229
  @scanning
136
230
  end
137
231
 
138
- # Configure scan parameters
232
+ # Update scan configuration parameters
233
+ #
234
+ # Allows modification of scan parameters after the scanner has been created.
235
+ # Only non-nil parameters will be updated.
236
+ #
237
+ # @param [Integer, nil] start_freq New starting frequency in Hz
238
+ # @param [Integer, nil] end_freq New ending frequency in Hz
239
+ # @param [Integer, nil] step_size New step size in Hz
240
+ # @param [Float, nil] dwell_time New dwell time in seconds
241
+ # @return [Scanner] self for method chaining
242
+ # @example Reconfigure scanner
243
+ # scanner.configure(start_freq: 400_000_000, step_size: 25_000)
139
244
  def configure(start_freq: nil, end_freq: nil, step_size: nil, dwell_time: nil)
140
245
  @start_freq = start_freq if start_freq
141
246
  @end_freq = end_freq if end_freq
@@ -144,6 +249,9 @@ module RTLSDR
144
249
  self
145
250
  end
146
251
 
252
+ # Return string representation of scanner
253
+ #
254
+ # @return [String] Human-readable scanner configuration
147
255
  def inspect
148
256
  "#<RTLSDR::Scanner #{@start_freq / 1e6}MHz-#{@end_freq / 1e6}MHz step=#{@step_size / 1e6}MHz dwell=#{@dwell_time}s>" # rubocop:disable Layout/LineLength
149
257
  end
@@ -1,5 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RTLSDR
4
- VERSION = "0.1.0"
4
+ # Current version of the RTL-SDR Ruby gem
5
+ #
6
+ # This constant defines the semantic version of the Ruby bindings.
7
+ # The version follows semantic versioning (semver) conventions:
8
+ # MAJOR.MINOR.PATCH where:
9
+ # - MAJOR: Backwards-incompatible API changes
10
+ # - MINOR: New features, backwards-compatible
11
+ # - PATCH: Bug fixes, backwards-compatible
12
+ #
13
+ # @return [String] Current gem version
14
+ # @since 0.1.0
15
+ VERSION = "0.1.2"
5
16
  end
data/lib/rtlsdr.rb CHANGED
@@ -37,16 +37,37 @@ require_relative "rtlsdr/scanner"
37
37
  module RTLSDR
38
38
  class << self
39
39
  # Get the number of connected RTL-SDR devices
40
+ #
41
+ # @return [Integer] Number of RTL-SDR devices found
42
+ # @example Check for devices
43
+ # if RTLSDR.device_count > 0
44
+ # puts "Found #{RTLSDR.device_count} RTL-SDR devices"
45
+ # end
40
46
  def device_count
41
47
  FFI.rtlsdr_get_device_count
42
48
  end
43
49
 
44
50
  # Get the name of a device by index
51
+ #
52
+ # @param [Integer] index Device index (0-based)
53
+ # @return [String] Device name string
54
+ # @example Get device name
55
+ # name = RTLSDR.device_name(0)
56
+ # puts "Device 0: #{name}"
45
57
  def device_name(index)
46
58
  FFI.rtlsdr_get_device_name(index)
47
59
  end
48
60
 
49
- # Get USB strings for a device by index
61
+ # Get USB device strings for a device by index
62
+ #
63
+ # Retrieves the USB manufacturer, product, and serial number strings
64
+ # for the specified device.
65
+ #
66
+ # @param [Integer] index Device index (0-based)
67
+ # @return [Hash, nil] Hash with :manufacturer, :product, :serial keys, or nil on error
68
+ # @example Get USB info
69
+ # usb_info = RTLSDR.device_usb_strings(0)
70
+ # puts "#{usb_info[:manufacturer]} #{usb_info[:product]}"
50
71
  def device_usb_strings(index)
51
72
  manufact = " " * 256
52
73
  product = " " * 256
@@ -63,6 +84,15 @@ module RTLSDR
63
84
  end
64
85
 
65
86
  # Find device index by serial number
87
+ #
88
+ # Searches for a device with the specified serial number and returns
89
+ # its index if found.
90
+ #
91
+ # @param [String] serial Serial number to search for
92
+ # @return [Integer, nil] Device index if found, nil otherwise
93
+ # @example Find device by serial
94
+ # index = RTLSDR.find_device_by_serial("00000001")
95
+ # device = RTLSDR.open(index) if index
66
96
  def find_device_by_serial(serial)
67
97
  result = FFI.rtlsdr_get_index_by_serial(serial)
68
98
  return nil if result.negative?
@@ -71,11 +101,32 @@ module RTLSDR
71
101
  end
72
102
 
73
103
  # Open a device and return a Device instance
104
+ #
105
+ # Creates and returns a new Device instance for the specified device index.
106
+ # The device will be automatically opened and ready for use.
107
+ #
108
+ # @param [Integer] index Device index to open (default: 0)
109
+ # @return [RTLSDR::Device] Device instance
110
+ # @raise [DeviceNotFoundError] if device doesn't exist
111
+ # @raise [DeviceOpenError] if device cannot be opened
112
+ # @example Open first device
113
+ # device = RTLSDR.open(0)
114
+ # device.center_freq = 100_000_000
74
115
  def open(index = 0)
75
116
  Device.new(index)
76
117
  end
77
118
 
78
- # List all available devices
119
+ # List all available devices with their information
120
+ #
121
+ # Returns an array of hashes containing information about all connected
122
+ # RTL-SDR devices, including index, name, and USB strings.
123
+ #
124
+ # @return [Array<Hash>] Array of device information hashes
125
+ # @example List all devices
126
+ # RTLSDR.devices.each do |device|
127
+ # puts "#{device[:index]}: #{device[:name]}"
128
+ # puts " #{device[:usb_strings][:manufacturer]} #{device[:usb_strings][:product]}"
129
+ # end
79
130
  def devices
80
131
  (0...device_count).map do |i|
81
132
  {
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rtlsdr
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - joshfng
@@ -30,15 +30,21 @@ email:
30
30
  executables:
31
31
  - rtlsdr
32
32
  extensions: []
33
- extra_rdoc_files: []
33
+ extra_rdoc_files:
34
+ - CHANGELOG.md
35
+ - LICENSE.txt
36
+ - README.md
34
37
  files:
35
38
  - ".rspec"
36
39
  - ".rubocop.yml"
37
40
  - ".ruby-version"
41
+ - ".yardconfig"
42
+ - ".yardopts"
38
43
  - CHANGELOG.md
39
44
  - LICENSE.txt
40
45
  - README.md
41
46
  - Rakefile
47
+ - doc_config.rb
42
48
  - examples/basic_usage.rb
43
49
  - examples/spectrum_analyzer.rb
44
50
  - exe/rtlsdr
@@ -58,7 +64,13 @@ metadata:
58
64
  homepage_uri: https://github.com/joshfng/rtlsdr-ruby
59
65
  source_code_uri: https://github.com/joshfng/rtlsdr-ruby
60
66
  changelog_uri: https://github.com/joshfng/rtlsdr-ruby/blob/main/CHANGELOG.md
61
- rdoc_options: []
67
+ documentation_uri: https://rubydoc.info/gems/rtlsdr
68
+ bug_tracker_uri: https://github.com/joshfng/rtlsdr-ruby/issues
69
+ rdoc_options:
70
+ - "--main"
71
+ - README.md
72
+ - "--line-numbers"
73
+ - "--all"
62
74
  require_paths:
63
75
  - lib
64
76
  required_ruby_version: !ruby/object:Gem::Requirement