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 +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +34 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +397 -0
- data/Rakefile +33 -0
- data/examples/basic_usage.rb +120 -0
- data/examples/spectrum_analyzer.rb +175 -0
- data/exe/rtlsdr +4 -0
- data/lib/rtlsdr/device.rb +482 -0
- data/lib/rtlsdr/dsp.rb +119 -0
- data/lib/rtlsdr/errors.rb +12 -0
- data/lib/rtlsdr/ffi.rb +138 -0
- data/lib/rtlsdr/scanner.rb +151 -0
- data/lib/rtlsdr/version.rb +5 -0
- data/lib/rtlsdr.rb +89 -0
- metadata +78 -0
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
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
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!"
|