rtlsdr 0.1.0

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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: a684113fd7fab1a02eedacad523203bc20c1e88a3f4d4c8ef7ebddcf5e86b92b
4
+ data.tar.gz: 23950e66b5bf1b36133479ef9adf62bfee9b27de76a11969063926af7dd485bc
5
+ SHA512:
6
+ metadata.gz: 10696f54f07b1d4ecb534ce286245e111e9c8068d68ff08772e8587238fc6fafbbf95ab55de5c891d5c0470034b377b44b67c09eaf3f27cec1cdf69a677bffcb
7
+ data.tar.gz: fe96888f3c93aa4947c7275cc74caf7585b222a973fd4cca06be7758516c66d8bfa71af5b442f5213b5521a1d44810f47d6e0faf22d85cfdad320333d6a9e07a
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,34 @@
1
+ plugins:
2
+ - rubocop-rake
3
+ - rubocop-rspec
4
+
5
+ AllCops:
6
+ TargetRubyVersion: 3.3
7
+ NewCops: enable
8
+ Exclude:
9
+ - 'examples/**/*'
10
+
11
+ Style/StringLiterals:
12
+ EnforcedStyle: double_quotes
13
+
14
+ Style/StringLiteralsInInterpolation:
15
+ EnforcedStyle: double_quotes
16
+
17
+ Metrics/ClassLength:
18
+ Max: 500
19
+ Metrics/MethodLength:
20
+ Max: 20
21
+ Metrics/BlockLength:
22
+ Max: 20
23
+ Metrics/AbcSize:
24
+ Max: 25
25
+ Metrics/PerceivedComplexity:
26
+ Max: 20
27
+ Metrics/CyclomaticComplexity:
28
+ Max: 20
29
+
30
+ Naming/MethodParameterName:
31
+ MinNameLength: 2
32
+
33
+ RSpec/MultipleExpectations:
34
+ Enabled: false
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 3.4.4
data/CHANGELOG.md ADDED
@@ -0,0 +1,7 @@
1
+ # Changelog
2
+
3
+ ## [Unreleased]
4
+
5
+ ## [0.1.0] - 2025-06-07
6
+
7
+ - Initial release
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2025 joshfng
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,397 @@
1
+ # RTL-SDR Ruby Gem
2
+
3
+ A comprehensive Ruby gem for interfacing with RTL-SDR (Software Defined Radio) devices. This gem provides both low-level FFI bindings that closely match the C API and high-level Ruby-idiomatic classes for easy use.
4
+
5
+ ## Features
6
+
7
+ - **Complete C API Coverage**: All librtlsdr functions are exposed through FFI
8
+ - **Ruby Idiomatic Interface**: High-level classes with Ruby conventions
9
+ - **Async/Sync Reading**: Both synchronous and asynchronous sample reading
10
+ - **Signal Processing**: Built-in DSP functions for common operations
11
+ - **Frequency Scanning**: Sweep frequencies and find active signals
12
+ - **Enumerable Interface**: Use Ruby's enumerable methods on sample streams
13
+ - **Error Handling**: Proper Ruby exceptions for different error conditions
14
+
15
+ ## Installation
16
+
17
+ First, make sure you have librtlsdr installed on your system:
18
+
19
+ ### Ubuntu/Debian
20
+
21
+ ```bash
22
+ sudo apt-get install librtlsdr-dev
23
+ ```
24
+
25
+ ### macOS (Homebrew)
26
+
27
+ ```bash
28
+ brew install librtlsdr
29
+ ```
30
+
31
+ ### From Source
32
+
33
+ If you need to build librtlsdr from source, the gem will automatically try to build it:
34
+
35
+ ```bash
36
+ git clone https://github.com/your-repo/rtlsdr-ruby.git
37
+ cd rtlsdr-ruby
38
+ bundle install
39
+ rake build_librtlsdr # This will build librtlsdr in librtlsdr/build/install/
40
+ ```
41
+
42
+ Then install the gem:
43
+
44
+ ```bash
45
+ gem install rtlsdr
46
+ ```
47
+
48
+ Or add to your Gemfile:
49
+
50
+ ```ruby
51
+ gem 'rtlsdr'
52
+ ```
53
+
54
+ ## Quick Start
55
+
56
+ ```ruby
57
+ require 'rtlsdr'
58
+
59
+ # List available devices
60
+ puts "Found #{RTLSDR.device_count} RTL-SDR devices:"
61
+ RTLSDR.devices.each_with_index do |device, i|
62
+ puts "#{i}: #{device[:name]} (#{device[:usb_strings][:product]})"
63
+ end
64
+
65
+ # Open first device
66
+ device = RTLSDR.open(0)
67
+
68
+ # Configure for FM radio
69
+ device.configure(
70
+ frequency: 100_500_000, # 100.5 MHz
71
+ sample_rate: 2_048_000, # 2.048 MHz
72
+ gain: 400 # 40.0 dB
73
+ )
74
+
75
+ # Read some samples
76
+ samples = device.read_samples(1024)
77
+ puts "Read #{samples.length} complex samples"
78
+
79
+ # Calculate average power
80
+ power = RTLSDR::DSP.average_power(samples)
81
+ power_db = 10 * Math.log10(power + 1e-10)
82
+ puts "Signal power: #{power_db.round(2)} dB"
83
+
84
+ device.close
85
+ ```
86
+
87
+ ## Device Control
88
+
89
+ ### Opening and Configuring Devices
90
+
91
+ ```ruby
92
+ # Open specific device by index
93
+ device = RTLSDR.open(0)
94
+
95
+ # Get device information
96
+ puts device.name # Device name
97
+ puts device.tuner_name # Tuner chip name
98
+ puts device.usb_strings # USB manufacturer, product, serial
99
+
100
+ # Configure all at once
101
+ device.configure(
102
+ frequency: 433_920_000, # 433.92 MHz
103
+ sample_rate: 2_048_000, # 2.048 MHz
104
+ gain: 300, # 30.0 dB (gains are in tenths of dB)
105
+ freq_correction: 15, # 15 PPM frequency correction
106
+ agc_mode: false, # Disable AGC
107
+ test_mode: false, # Disable test mode
108
+ bias_tee: false # Disable bias tee
109
+ )
110
+
111
+ # Or configure individually
112
+ device.center_freq = 433_920_000
113
+ device.sample_rate = 2_048_000
114
+ device.manual_gain_mode! # Enable manual gain
115
+ device.tuner_gain = 300 # 30.0 dB
116
+
117
+ # Check available gains
118
+ puts device.tuner_gains.map { |g| g / 10.0 } # [0.0, 0.9, 1.4, 2.7, ...]
119
+ ```
120
+
121
+ ### Reading Samples
122
+
123
+ ```ruby
124
+ # Synchronous reading
125
+ samples = device.read_samples(4096) # Returns array of Complex numbers
126
+
127
+ # Asynchronous reading
128
+ device.read_samples_async do |samples|
129
+ # Process samples in real-time
130
+ power = RTLSDR::DSP.average_power(samples)
131
+ puts "Power: #{10 * Math.log10(power + 1e-10)} dB"
132
+ end
133
+
134
+ # Stop async reading
135
+ device.cancel_async
136
+
137
+ # Enumerable interface - read continuously
138
+ device.each(samples_per_read: 1024) do |samples|
139
+ # Process each batch of samples
140
+ break if some_condition
141
+ end
142
+ ```
143
+
144
+ ## Signal Processing
145
+
146
+ The gem includes built-in DSP functions:
147
+
148
+ ```ruby
149
+ samples = device.read_samples(8192)
150
+
151
+ # Power calculations
152
+ avg_power = RTLSDR::DSP.average_power(samples)
153
+ power_db = 10 * Math.log10(avg_power + 1e-10)
154
+
155
+ # Convert to magnitude and phase
156
+ magnitude = RTLSDR::DSP.magnitude(samples)
157
+ phase = RTLSDR::DSP.phase(samples)
158
+
159
+ # Remove DC component
160
+ filtered = RTLSDR::DSP.remove_dc(samples)
161
+
162
+ # Frequency estimation
163
+ freq_offset = RTLSDR::DSP.estimate_frequency(samples, device.sample_rate)
164
+
165
+ # Power spectrum (simple implementation)
166
+ power_spectrum = RTLSDR::DSP.power_spectrum(samples, 1024)
167
+ peak_bin, peak_power = RTLSDR::DSP.find_peak(power_spectrum)
168
+ ```
169
+
170
+ ## Frequency Scanning
171
+
172
+ Scan frequency ranges to find active signals:
173
+
174
+ ```ruby
175
+ # Create a scanner
176
+ scanner = RTLSDR::Scanner.new(device,
177
+ start_freq: 88_000_000, # 88 MHz
178
+ end_freq: 108_000_000, # 108 MHz
179
+ step_size: 200_000, # 200 kHz steps
180
+ dwell_time: 0.1 # 100ms per frequency
181
+ )
182
+
183
+ # Perform power sweep
184
+ results = scanner.power_sweep(samples_per_freq: 2048)
185
+ results.each do |freq, power_db|
186
+ puts "#{freq/1e6} MHz: #{power_db} dB" if power_db > -60
187
+ end
188
+
189
+ # Find peaks above threshold
190
+ peaks = scanner.find_peaks(threshold: -50, samples_per_freq: 4096)
191
+ peaks.each do |peak|
192
+ puts "#{peak[:frequency]/1e6} MHz: #{peak[:power_db]} dB"
193
+ end
194
+
195
+ # Real-time scanning with callback
196
+ scanner.scan(samples_per_freq: 1024) do |result|
197
+ if result[:power] > some_threshold
198
+ puts "Signal found at #{result[:frequency]/1e6} MHz"
199
+ end
200
+ end
201
+ ```
202
+
203
+ ## Advanced Features
204
+
205
+ ### EEPROM Access
206
+
207
+ ```ruby
208
+ # Read EEPROM
209
+ data = device.read_eeprom(0, 256) # Read 256 bytes from offset 0
210
+
211
+ # Write EEPROM (be careful!)
212
+ device.write_eeprom([0x01, 0x02, 0x03], 0) # Write 3 bytes at offset 0
213
+ ```
214
+
215
+ ### Crystal Frequency Adjustment
216
+
217
+ ```ruby
218
+ # Set custom crystal frequencies
219
+ device.set_xtal_freq(28_800_000, 28_800_000) # RTL and tuner crystal freqs
220
+
221
+ # Get current crystal frequencies
222
+ rtl_freq, tuner_freq = device.xtal_freq
223
+ ```
224
+
225
+ ### Direct Sampling Mode
226
+
227
+ ```ruby
228
+ # Enable direct sampling (for HF reception)
229
+ device.direct_sampling = 1 # I-ADC input
230
+ device.direct_sampling = 2 # Q-ADC input
231
+ device.direct_sampling = 0 # Disabled
232
+ ```
233
+
234
+ ### Bias Tee Control
235
+
236
+ ```ruby
237
+ # Enable bias tee on GPIO 0
238
+ device.bias_tee = true
239
+
240
+ # Enable bias tee on specific GPIO
241
+ device.set_bias_tee_gpio(1, true)
242
+ ```
243
+
244
+ ## Error Handling
245
+
246
+ The gem provides specific exception types:
247
+
248
+ ```ruby
249
+ begin
250
+ device = RTLSDR.open(99) # Non-existent device
251
+ rescue RTLSDR::DeviceNotFoundError => e
252
+ puts "Device not found: #{e.message}"
253
+ rescue RTLSDR::DeviceOpenError => e
254
+ puts "Could not open device: #{e.message}"
255
+ rescue RTLSDR::Error => e
256
+ puts "RTL-SDR error: #{e.message}"
257
+ end
258
+ ```
259
+
260
+ Exception types:
261
+
262
+ - `RTLSDR::Error` - Base error class
263
+ - `RTLSDR::DeviceNotFoundError` - Device doesn't exist
264
+ - `RTLSDR::DeviceOpenError` - Can't open device (in use, permissions, etc.)
265
+ - `RTLSDR::DeviceNotOpenError` - Device is not open
266
+ - `RTLSDR::InvalidArgumentError` - Invalid parameter
267
+ - `RTLSDR::OperationFailedError` - Operation failed
268
+ - `RTLSDR::EEPROMError` - EEPROM access error
269
+
270
+ ## Examples
271
+
272
+ See the `examples/` directory for complete examples:
273
+
274
+ - `basic_usage.rb` - Basic device control and sample reading
275
+ - `spectrum_analyzer.rb` - Advanced spectrum analysis and scanning
276
+
277
+ Run examples:
278
+
279
+ ```bash
280
+ ruby examples/basic_usage.rb
281
+ ruby examples/spectrum_analyzer.rb
282
+ ```
283
+
284
+ ## API Reference
285
+
286
+ ### Module Methods
287
+
288
+ - `RTLSDR.device_count` - Number of connected devices
289
+ - `RTLSDR.device_name(index)` - Get device name
290
+ - `RTLSDR.device_usb_strings(index)` - Get USB strings
291
+ - `RTLSDR.find_device_by_serial(serial)` - Find device by serial number
292
+ - `RTLSDR.open(index)` - Open device and return Device instance
293
+ - `RTLSDR.devices` - List all devices with info
294
+
295
+ ### Device Methods
296
+
297
+ #### Device Control
298
+
299
+ - `#open?`, `#closed?` - Check device state
300
+ - `#close` - Close device
301
+ - `#configure(options)` - Configure multiple settings at once
302
+ - `#info` - Get device information hash
303
+
304
+ #### Frequency Control
305
+
306
+ - `#center_freq`, `#center_freq=` - Center frequency (Hz)
307
+ - `#frequency`, `#frequency=` - Alias for center_freq
308
+ - `#freq_correction`, `#freq_correction=` - Frequency correction (PPM)
309
+ - `#set_xtal_freq(rtl_freq, tuner_freq)` - Set crystal frequencies
310
+ - `#xtal_freq` - Get crystal frequencies
311
+
312
+ #### Gain Control
313
+
314
+ - `#tuner_gains` - Available gain values (tenths of dB)
315
+ - `#tuner_gain`, `#tuner_gain=` - Current gain (tenths of dB)
316
+ - `#gain`, `#gain=` - Alias for tuner_gain
317
+ - `#tuner_gain_mode=` - Set manual (true) or auto (false) gain
318
+ - `#manual_gain_mode!`, `#auto_gain_mode!` - Convenience methods
319
+ - `#set_tuner_if_gain(stage, gain)` - Set IF gain for specific stage
320
+ - `#tuner_bandwidth=` - Set tuner bandwidth
321
+
322
+ #### Sample Rate and Modes
323
+
324
+ - `#sample_rate`, `#sample_rate=` - Sample rate (Hz)
325
+ - `#test_mode=`, `#test_mode!` - Enable test mode
326
+ - `#agc_mode=`, `#agc_mode!` - Enable AGC
327
+ - `#direct_sampling`, `#direct_sampling=` - Direct sampling mode
328
+ - `#offset_tuning`, `#offset_tuning=`, `#offset_tuning!` - Offset tuning
329
+ - `#bias_tee=`, `#bias_tee!` - Bias tee control
330
+ - `#set_bias_tee_gpio(gpio, enabled)` - GPIO-specific bias tee
331
+
332
+ #### Sample Reading
333
+
334
+ - `#read_samples(count)` - Read complex samples synchronously
335
+ - `#read_sync(length)` - Read raw bytes synchronously
336
+ - `#read_samples_async(&block)` - Read samples asynchronously
337
+ - `#read_async(&block)` - Read raw bytes asynchronously
338
+ - `#cancel_async` - Stop async reading
339
+ - `#streaming?` - Check if async reading is active
340
+ - `#reset_buffer` - Reset device buffer
341
+ - `#each(options, &block)` - Enumerable interface
342
+
343
+ #### EEPROM Access
344
+
345
+ - `#read_eeprom(offset, length)` - Read EEPROM data
346
+ - `#write_eeprom(data, offset)` - Write EEPROM data
347
+
348
+ ### DSP Functions
349
+
350
+ - `RTLSDR::DSP.iq_to_complex(data)` - Convert IQ bytes to complex samples
351
+ - `RTLSDR::DSP.average_power(samples)` - Calculate average power
352
+ - `RTLSDR::DSP.power_spectrum(samples, window_size)` - Power spectrum
353
+ - `RTLSDR::DSP.find_peak(power_spectrum)` - Find peak in spectrum
354
+ - `RTLSDR::DSP.remove_dc(samples, alpha)` - DC removal filter
355
+ - `RTLSDR::DSP.magnitude(samples)` - Convert to magnitude
356
+ - `RTLSDR::DSP.phase(samples)` - Extract phase information
357
+ - `RTLSDR::DSP.estimate_frequency(samples, sample_rate)` - Frequency estimation
358
+
359
+ ### Scanner Class
360
+
361
+ - `Scanner.new(device, options)` - Create frequency scanner
362
+ - `#scan(&block)` - Perform frequency sweep with callback
363
+ - `#scan_async(&block)` - Async frequency sweep
364
+ - `#power_sweep(options)` - Get power measurements across frequencies
365
+ - `#find_peaks(options)` - Find signal peaks above threshold
366
+ - `#stop` - Stop scanning
367
+ - `#configure(options)` - Update scan parameters
368
+
369
+ ## Contributing
370
+
371
+ 1. Fork the repository
372
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
373
+ 3. Write tests for your changes
374
+ 4. Make sure all tests pass (`rake spec`)
375
+ 5. Commit your changes (`git commit -am 'Add some feature'`)
376
+ 6. Push to the branch (`git push origin my-new-feature`)
377
+ 7. Create a Pull Request
378
+
379
+ ## License
380
+
381
+ This gem is licensed under the GPL-2.0 license, the same as librtlsdr.
382
+
383
+ ## Requirements
384
+
385
+ - Ruby 2.7 or later
386
+ - librtlsdr (installed system-wide or built locally)
387
+ - FFI gem
388
+
389
+ ## Supported Platforms
390
+
391
+ - Linux (tested on Ubuntu)
392
+ - macOS (tested on macOS 10.15+)
393
+ - Windows (should work but not extensively tested)
394
+
395
+ ## Credits
396
+
397
+ This gem provides Ruby bindings for [librtlsdr](https://github.com/steve-m/librtlsdr), the excellent RTL-SDR library by Steve Markgraf and contributors.
data/Rakefile ADDED
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+ require "rake/extensiontask"
6
+ require "rubocop/rake_task"
7
+
8
+ RSpec::Core::RakeTask.new(:spec)
9
+
10
+ RuboCop::RakeTask.new
11
+
12
+ Rake::ExtensionTask.new("rtlsdr") do |ext|
13
+ ext.lib_dir = "lib/rtlsdr"
14
+ end
15
+
16
+ task default: %i[spec rubocop]
17
+
18
+ # Custom task to build librtlsdr if needed
19
+ desc "Build librtlsdr from source (clones, configures, and compiles)"
20
+ task :build_librtlsdr do
21
+ system("git clone https://github.com/steve-m/librtlsdr.git librtlsdr")
22
+ Dir.chdir("librtlsdr") do
23
+ system("mkdir -p build")
24
+ Dir.chdir("build") do
25
+ system("cmake .. -DCMAKE_INSTALL_PREFIX=#{Dir.pwd}/install")
26
+ system("make")
27
+ system("make install")
28
+ end
29
+ end
30
+ end
31
+
32
+ desc "Compile native extensions (depends on librtlsdr)"
33
+ task compile: :build_librtlsdr
@@ -0,0 +1,120 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "../lib/rtlsdr"
5
+
6
+ puts "RTL-SDR Ruby Gem Basic Usage Example"
7
+ puts "====================================="
8
+
9
+ # List all available devices
10
+ puts "\nAvailable RTL-SDR devices:"
11
+ RTLSDR.devices.each_with_index do |device, i|
12
+ puts " #{i}: #{device[:name]}"
13
+ puts " Manufacturer: #{device[:usb_strings][:manufacturer]}"
14
+ puts " Product: #{device[:usb_strings][:product]}"
15
+ puts " Serial: #{device[:usb_strings][:serial]}"
16
+ end
17
+
18
+ if RTLSDR.device_count.zero?
19
+ puts "No RTL-SDR devices found. Make sure your device is connected."
20
+ exit 1
21
+ end
22
+
23
+ begin
24
+ # Open the first device
25
+ puts "\nOpening device 0..."
26
+ device = RTLSDR.open(0)
27
+
28
+ puts "Device opened successfully!"
29
+ puts device.inspect
30
+
31
+ # Get device information
32
+ puts "\nDevice Information:"
33
+ puts " Name: #{device.name}"
34
+ puts " Tuner: #{device.tuner_name}"
35
+ puts " USB Info: #{device.usb_strings}"
36
+
37
+ # Configure the device
38
+ puts "\nConfiguring device..."
39
+ device.configure(
40
+ frequency: 100_000_000, # 100 MHz
41
+ sample_rate: 2_048_000, # 2.048 MHz sample rate
42
+ gain: 400 # 40.0 dB gain (gains are in tenths of dB)
43
+ )
44
+
45
+ puts "Configuration complete:"
46
+ puts " Center frequency: #{device.center_freq / 1e6} MHz"
47
+ puts " Sample rate: #{device.sample_rate / 1e6} MHz"
48
+ puts " Current gain: #{device.tuner_gain / 10.0} dB"
49
+ puts " Available gains: #{device.tuner_gains.map { |g| g / 10.0 }} dB"
50
+
51
+ # Read some samples
52
+ puts "\nReading 1024 samples..."
53
+ samples = device.read_samples(1024)
54
+
55
+ puts "Read #{samples.length} complex samples"
56
+ puts "First 5 samples:"
57
+ samples.first(5).each_with_index do |sample, i|
58
+ puts " Sample #{i}: #{sample.real.round(4)} + #{sample.imag.round(4)}i (magnitude: #{sample.abs.round(4)})"
59
+ end
60
+
61
+ # Calculate some basic statistics
62
+ avg_power = RTLSDR::DSP.average_power(samples)
63
+ power_db = 10 * Math.log10(avg_power + 1e-10)
64
+ magnitudes = RTLSDR::DSP.magnitude(samples)
65
+
66
+ puts "\nSignal Analysis:"
67
+ puts " Average power: #{power_db.round(2)} dB"
68
+ puts " Peak magnitude: #{magnitudes.max.round(4)}"
69
+ puts " Min magnitude: #{magnitudes.min.round(4)}"
70
+ puts " RMS magnitude: #{Math.sqrt(magnitudes.map { |m| m**2 }.sum / magnitudes.length).round(4)}"
71
+
72
+ # Demonstrate async reading for a short time
73
+ puts "\nDemonstrating async reading for 2 seconds..."
74
+
75
+ sample_count = 0
76
+ start_time = Time.now
77
+
78
+ # Start async reading
79
+ thread = device.read_samples_async(buffer_count: 4, buffer_length: 16_384) do |samples|
80
+ sample_count += samples.length
81
+ elapsed = Time.now - start_time
82
+
83
+ if elapsed >= 2.0
84
+ device.cancel_async
85
+ break
86
+ end
87
+
88
+ power = RTLSDR::DSP.average_power(samples)
89
+ power_db = 10 * Math.log10(power + 1e-10)
90
+ print "\rSamples received: #{sample_count}, Power: #{power_db.round(2)} dB"
91
+ $stdout.flush
92
+ end
93
+
94
+ # Wait for async reading to complete
95
+ thread.join
96
+ puts "\nAsync reading complete. Total samples: #{sample_count}"
97
+
98
+ # Demonstrate enumerable interface
99
+ puts "\nDemonstrating enumerable interface (reading 3 batches)..."
100
+ device.each(samples_per_read: 512).with_index do |samples, batch|
101
+ power = RTLSDR::DSP.average_power(samples)
102
+ power_db = 10 * Math.log10(power + 1e-10)
103
+ puts " Batch #{batch + 1}: #{samples.length} samples, power: #{power_db.round(2)} dB"
104
+
105
+ break if batch >= 2 # Only read 3 batches
106
+ end
107
+ rescue RTLSDR::Error => e
108
+ puts "RTL-SDR Error: #{e.message}"
109
+ puts "Make sure your RTL-SDR device is properly connected and not in use by another application."
110
+ exit 1
111
+ rescue Interrupt
112
+ puts "\nInterrupted by user"
113
+ ensure
114
+ if device
115
+ device.close
116
+ puts "\nDevice closed."
117
+ end
118
+ end
119
+
120
+ puts "\nExample completed successfully!"