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.
- Complete C API Coverage: All librtlsdr functions are exposed through FFI
- Ruby Idiomatic Interface: High-level classes with Ruby conventions
- Async/Sync Reading: Both synchronous and asynchronous sample reading
- Signal Processing: Built-in DSP functions for common operations
- Frequency Scanning: Sweep frequencies and find active signals
- Enumerable Interface: Use Ruby's enumerable methods on sample streams
- Error Handling: Proper Ruby exceptions for different error conditions
First, make sure you have librtlsdr installed on your system:
sudo apt-get install librtlsdr-dev
brew install librtlsdr
If you need to build librtlsdr from source, the gem will automatically try to build it:
git clone https://github.com/your-repo/rtlsdr-ruby.git
cd rtlsdr-ruby
bundle install
rake build_librtlsdr # This will build librtlsdr in librtlsdr/build/install/
Then install the gem:
gem install rtlsdr
Or add to your Gemfile:
gem 'rtlsdr'
require 'rtlsdr'
# List available devices
puts "Found #{RTLSDR.device_count} RTL-SDR devices:"
RTLSDR.devices.each_with_index do |device, i|
puts "#{i}: #{device[:name]} (#{device[:usb_strings][:product]})"
end
# Open first device
device = RTLSDR.open(0)
# Configure for FM radio
device.configure(
frequency: 100_500_000, # 100.5 MHz
sample_rate: 2_048_000, # 2.048 MHz
gain: 400 # 40.0 dB
)
# Read some samples
samples = device.read_samples(1024)
puts "Read #{samples.length} complex samples"
# Calculate average power
power = RTLSDR::DSP.average_power(samples)
power_db = 10 * Math.log10(power + 1e-10)
puts "Signal power: #{power_db.round(2)} dB"
device.close
# Open specific device by index
device = RTLSDR.open(0)
# Get device information
puts device.name # Device name
puts device.tuner_name # Tuner chip name
puts device.usb_strings # USB manufacturer, product, serial
# Configure all at once
device.configure(
frequency: 433_920_000, # 433.92 MHz
sample_rate: 2_048_000, # 2.048 MHz
gain: 300, # 30.0 dB (gains are in tenths of dB)
freq_correction: 15, # 15 PPM frequency correction
agc_mode: false, # Disable AGC
test_mode: false, # Disable test mode
bias_tee: false # Disable bias tee
)
# Or configure individually
device.center_freq = 433_920_000
device.sample_rate = 2_048_000
device.manual_gain_mode! # Enable manual gain
device.tuner_gain = 300 # 30.0 dB
# Check available gains
puts device.tuner_gains.map { |g| g / 10.0 } # [0.0, 0.9, 1.4, 2.7, ...]
# Synchronous reading
samples = device.read_samples(4096) # Returns array of Complex numbers
# Asynchronous reading
device.read_samples_async do |samples|
# Process samples in real-time
power = RTLSDR::DSP.average_power(samples)
puts "Power: #{10 * Math.log10(power + 1e-10)} dB"
end
# Stop async reading
device.cancel_async
# Enumerable interface - read continuously
device.each(samples_per_read: 1024) do |samples|
# Process each batch of samples
break if some_condition
end
The gem includes built-in DSP functions:
samples = device.read_samples(8192)
# Power calculations
avg_power = RTLSDR::DSP.average_power(samples)
power_db = 10 * Math.log10(avg_power + 1e-10)
# Convert to magnitude and phase
magnitude = RTLSDR::DSP.magnitude(samples)
phase = RTLSDR::DSP.phase(samples)
# Remove DC component
filtered = RTLSDR::DSP.remove_dc(samples)
# Frequency estimation
freq_offset = RTLSDR::DSP.estimate_frequency(samples, device.sample_rate)
# Power spectrum (simple implementation)
power_spectrum = RTLSDR::DSP.power_spectrum(samples, 1024)
peak_bin, peak_power = RTLSDR::DSP.find_peak(power_spectrum)
Scan frequency ranges to find active signals:
# Create a 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 steps
dwell_time: 0.1 # 100ms per frequency
)
# Perform power sweep
results = scanner.power_sweep(samples_per_freq: 2048)
results.each do |freq, power_db|
puts "#{freq/1e6} MHz: #{power_db} dB" if power_db > -60
end
# Find peaks above threshold
peaks = scanner.find_peaks(threshold: -50, samples_per_freq: 4096)
peaks.each do |peak|
puts "#{peak[:frequency]/1e6} MHz: #{peak[:power_db]} dB"
end
# Real-time scanning with callback
scanner.scan(samples_per_freq: 1024) do |result|
if result[:power] > some_threshold
puts "Signal found at #{result[:frequency]/1e6} MHz"
end
end
# Read EEPROM
data = device.read_eeprom(0, 256) # Read 256 bytes from offset 0
# Write EEPROM (be careful!)
device.write_eeprom([0x01, 0x02, 0x03], 0) # Write 3 bytes at offset 0
# Set custom crystal frequencies
device.set_xtal_freq(28_800_000, 28_800_000) # RTL and tuner crystal freqs
# Get current crystal frequencies
rtl_freq, tuner_freq = device.xtal_freq
# Enable direct sampling (for HF reception)
device.direct_sampling = 1 # I-ADC input
device.direct_sampling = 2 # Q-ADC input
device.direct_sampling = 0 # Disabled
# Enable bias tee on GPIO 0
device.bias_tee = true
# Enable bias tee on specific GPIO
device.set_bias_tee_gpio(1, true)
The gem provides specific exception types:
begin
device = RTLSDR.open(99) # Non-existent device
rescue RTLSDR::DeviceNotFoundError => e
puts "Device not found: #{e.message}"
rescue RTLSDR::DeviceOpenError => e
puts "Could not open device: #{e.message}"
rescue RTLSDR::Error => e
puts "RTL-SDR error: #{e.message}"
end
Exception types:
RTLSDR::Error
- Base error classRTLSDR::DeviceNotFoundError
- Device doesn't existRTLSDR::DeviceOpenError
- Can't open device (in use, permissions, etc.)RTLSDR::DeviceNotOpenError
- Device is not openRTLSDR::InvalidArgumentError
- Invalid parameterRTLSDR::OperationFailedError
- Operation failedRTLSDR::EEPROMError
- EEPROM access error
See the examples/
directory for complete examples:
basic_usage.rb
- Basic device control and sample readingspectrum_analyzer.rb
- Advanced spectrum analysis and scanning
Run examples:
ruby examples/basic_usage.rb
ruby examples/spectrum_analyzer.rb
RTLSDR.device_count
- Number of connected devicesRTLSDR.device_name(index)
- Get device nameRTLSDR.device_usb_strings(index)
- Get USB stringsRTLSDR.find_device_by_serial(serial)
- Find device by serial numberRTLSDR.open(index)
- Open device and return Device instanceRTLSDR.devices
- List all devices with info
#open?
,#closed?
- Check device state#close
- Close device#configure(options)
- Configure multiple settings at once#info
- Get device information hash
#center_freq
,#center_freq=
- Center frequency (Hz)#frequency
,#frequency=
- Alias for center_freq#freq_correction
,#freq_correction=
- Frequency correction (PPM)#set_xtal_freq(rtl_freq, tuner_freq)
- Set crystal frequencies#xtal_freq
- Get crystal frequencies
#tuner_gains
- Available gain values (tenths of dB)#tuner_gain
,#tuner_gain=
- Current gain (tenths of dB)#gain
,#gain=
- Alias for tuner_gain#tuner_gain_mode=
- Set manual (true) or auto (false) gain#manual_gain_mode!
,#auto_gain_mode!
- Convenience methods#set_tuner_if_gain(stage, gain)
- Set IF gain for specific stage#tuner_bandwidth=
- Set tuner bandwidth
#sample_rate
,#sample_rate=
- Sample rate (Hz)#test_mode=
,#test_mode!
- Enable test mode#agc_mode=
,#agc_mode!
- Enable AGC#direct_sampling
,#direct_sampling=
- Direct sampling mode#offset_tuning
,#offset_tuning=
,#offset_tuning!
- Offset tuning#bias_tee=
,#bias_tee!
- Bias tee control#set_bias_tee_gpio(gpio, enabled)
- GPIO-specific bias tee
#read_samples(count)
- Read complex samples synchronously#read_sync(length)
- Read raw bytes synchronously#read_samples_async(&block)
- Read samples asynchronously#read_async(&block)
- Read raw bytes asynchronously#cancel_async
- Stop async reading#streaming?
- Check if async reading is active#reset_buffer
- Reset device buffer#each(options, &block)
- Enumerable interface
#read_eeprom(offset, length)
- Read EEPROM data#write_eeprom(data, offset)
- Write EEPROM data
RTLSDR::DSP.iq_to_complex(data)
- Convert IQ bytes to complex samplesRTLSDR::DSP.average_power(samples)
- Calculate average powerRTLSDR::DSP.power_spectrum(samples, window_size)
- Power spectrumRTLSDR::DSP.find_peak(power_spectrum)
- Find peak in spectrumRTLSDR::DSP.remove_dc(samples, alpha)
- DC removal filterRTLSDR::DSP.magnitude(samples)
- Convert to magnitudeRTLSDR::DSP.phase(samples)
- Extract phase informationRTLSDR::DSP.estimate_frequency(samples, sample_rate)
- Frequency estimation
Scanner.new(device, options)
- Create frequency scanner#scan(&block)
- Perform frequency sweep with callback#scan_async(&block)
- Async frequency sweep#power_sweep(options)
- Get power measurements across frequencies#find_peaks(options)
- Find signal peaks above threshold#stop
- Stop scanning#configure(options)
- Update scan parameters
- Fork the repository
- Create your feature branch (
git checkout -b my-new-feature
) - Write tests for your changes
- Make sure all tests pass (
rake spec
) - Commit your changes (
git commit -am 'Add some feature'
) - Push to the branch (
git push origin my-new-feature
) - Create a Pull Request
This gem is licensed under the MIT license.
- Ruby 2.7 or later
- librtlsdr (installed system-wide or built locally)
- FFI gem
- Linux (tested on Ubuntu)
- macOS (tested on macOS 10.15+)
- Windows (should work but not extensively tested)
This gem provides Ruby bindings for librtlsdr, the excellent RTL-SDR library by Steve Markgraf and contributors.