Class: RTLSDR::TcpClient

Inherits:
Object
  • Object
show all
Includes:
Enumerable
Defined in:
lib/rtlsdr/tcp_client.rb

Overview

TCP client for connecting to rtl_tcp servers

TcpClient provides a network interface to RTL-SDR devices that are shared over the network using the rtl_tcp utility. It implements the same interface as RTLSDR::Device, allowing seamless switching between local and remote devices.

rtl_tcp is a utility that makes RTL-SDR devices available over TCP/IP. Run it on a remote machine with: rtl_tcp -a 0.0.0.0

Examples:

Connect to remote device

device = RTLSDR.connect("192.168.1.100", 1234)
device.sample_rate = 2_048_000
device.center_freq = 100_000_000
device.gain = 400
samples = device.read_samples(1024)
device.close

Using configure method

device = RTLSDR.connect("localhost")
device.configure(
  frequency: 100_000_000,
  sample_rate: 2_048_000,
  gain: 400
)

Since:

  • 0.3.0

Constant Summary collapse

CMD_SET_FREQUENCY =

rtl_tcp command codes

Since:

  • 0.3.0

0x01
CMD_SET_SAMPLE_RATE =

Since:

  • 0.3.0

0x02
CMD_SET_GAIN_MODE =

Since:

  • 0.3.0

0x03
CMD_SET_GAIN =

Since:

  • 0.3.0

0x04
CMD_SET_FREQ_CORRECTION =

Since:

  • 0.3.0

0x05
CMD_SET_IF_GAIN =

Since:

  • 0.3.0

0x06
CMD_SET_TEST_MODE =

Since:

  • 0.3.0

0x07
CMD_SET_AGC_MODE =

Since:

  • 0.3.0

0x08
CMD_SET_DIRECT_SAMPLING =

Since:

  • 0.3.0

0x09
CMD_SET_OFFSET_TUNING =

Since:

  • 0.3.0

0x0a
CMD_SET_RTL_XTAL =

Since:

  • 0.3.0

0x0b
CMD_SET_TUNER_XTAL =

Since:

  • 0.3.0

0x0c
CMD_SET_GAIN_BY_INDEX =

Since:

  • 0.3.0

0x0d
CMD_SET_BIAS_TEE =

Since:

  • 0.3.0

0x0e
DEFAULT_PORT =

Default rtl_tcp port

Since:

  • 0.3.0

1234

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(host, port = DEFAULT_PORT, timeout: 10) ⇒ TcpClient

Create a new TCP client connection to an rtl_tcp server

Parameters:

  • host (String)

    Hostname or IP address of rtl_tcp server

  • port (Integer) (defaults to: DEFAULT_PORT)

    Port number (default: 1234)

  • timeout (Integer) (defaults to: 10)

    Connection timeout in seconds (default: 10)

Raises:

Since:

  • 0.3.0



71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
# File 'lib/rtlsdr/tcp_client.rb', line 71

def initialize(host, port = DEFAULT_PORT, timeout: 10)
  @host = host
  @port = port
  @timeout = timeout
  @socket = nil
  @streaming = false
  @mutex = Mutex.new

  # Cached configuration values (rtl_tcp doesn't support reading back)
  @center_freq = 0
  @sample_rate = 0
  @tuner_gain = 0
  @freq_correction = 0
  @direct_sampling = 0
  @offset_tuning = false
  @agc_mode = false
  @test_mode = false
  @gain_mode_manual = false

  connect_to_server
end

Instance Attribute Details

#center_freqInteger Also known as: frequency

Get center frequency

Returns:

  • (Integer)

    Last set frequency in Hz

Since:

  • 0.3.0



162
163
164
# File 'lib/rtlsdr/tcp_client.rb', line 162

def center_freq
  @center_freq
end

#direct_samplingInteger

Get direct sampling mode

Returns:

  • (Integer)

    Last set direct sampling mode

Since:

  • 0.3.0



325
326
327
# File 'lib/rtlsdr/tcp_client.rb', line 325

def direct_sampling
  @direct_sampling
end

#freq_correctionInteger

Get frequency correction

Returns:

  • (Integer)

    Last set frequency correction in PPM

Since:

  • 0.3.0



178
179
180
# File 'lib/rtlsdr/tcp_client.rb', line 178

def freq_correction
  @freq_correction
end

#gain_countInteger (readonly)

Returns Number of gain values supported.

Returns:

  • (Integer)

    Number of gain values supported

Since:

  • 0.3.0



62
63
64
# File 'lib/rtlsdr/tcp_client.rb', line 62

def gain_count
  @gain_count
end

#hostString (readonly)

Returns Remote host address.

Returns:

  • (String)

    Remote host address

Since:

  • 0.3.0



56
57
58
# File 'lib/rtlsdr/tcp_client.rb', line 56

def host
  @host
end

#offset_tuningBoolean

Get offset tuning mode

Returns:

  • (Boolean)

    Last set offset tuning state

Since:

  • 0.3.0



339
340
341
# File 'lib/rtlsdr/tcp_client.rb', line 339

def offset_tuning
  @offset_tuning
end

#portInteger (readonly)

Returns Remote port number.

Returns:

  • (Integer)

    Remote port number

Since:

  • 0.3.0



58
59
60
# File 'lib/rtlsdr/tcp_client.rb', line 58

def port
  @port
end

#sample_rateInteger Also known as: samp_rate

Get current sample rate

Returns:

  • (Integer)

    Last set sample rate in Hz

Since:

  • 0.3.0



275
276
277
# File 'lib/rtlsdr/tcp_client.rb', line 275

def sample_rate
  @sample_rate
end

#tuner_gainInteger Also known as: gain

Get current tuner gain

Returns:

  • (Integer)

    Last set gain in tenths of dB

Since:

  • 0.3.0



216
217
218
# File 'lib/rtlsdr/tcp_client.rb', line 216

def tuner_gain
  @tuner_gain
end

#tuner_typeInteger (readonly)

Returns Tuner type from server.

Returns:

  • (Integer)

    Tuner type from server

Since:

  • 0.3.0



60
61
62
# File 'lib/rtlsdr/tcp_client.rb', line 60

def tuner_type
  @tuner_type
end

Instance Method Details

#agc_mode!Boolean

Enable AGC mode

Returns:

  • (Boolean)

    true

Since:

  • 0.3.0



310
311
312
# File 'lib/rtlsdr/tcp_client.rb', line 310

def agc_mode!
  self.agc_mode = true
end

#agc_mode=(enabled) ⇒ Object

Set AGC mode

Parameters:

  • enabled (Boolean)

    true to enable AGC

Since:

  • 0.3.0



301
302
303
304
305
# File 'lib/rtlsdr/tcp_client.rb', line 301

def agc_mode=(enabled)
  mode = enabled ? 1 : 0
  send_command(CMD_SET_AGC_MODE, mode)
  @agc_mode = enabled
end

#auto_gain_mode!Boolean

Enable automatic gain mode

Returns:

  • (Boolean)

    false

Since:

  • 0.3.0



240
241
242
# File 'lib/rtlsdr/tcp_client.rb', line 240

def auto_gain_mode!
  self.tuner_gain_mode = false
end

#bias_tee!Boolean

Enable bias tee (alias)

Returns:

  • (Boolean)

    true

Since:

  • 0.3.0



375
376
377
# File 'lib/rtlsdr/tcp_client.rb', line 375

def bias_tee!
  enable_bias_tee
end

#bias_tee=(enabled) ⇒ Object

Set bias tee state

Parameters:

  • enabled (Boolean)

    true to enable bias tee

Since:

  • 0.3.0



353
354
355
356
# File 'lib/rtlsdr/tcp_client.rb', line 353

def bias_tee=(enabled)
  mode = enabled ? 1 : 0
  send_command(CMD_SET_BIAS_TEE, mode)
end

#cancel_asyncvoid

This method returns an undefined value.

Cancel asynchronous reading

Since:

  • 0.3.0



514
515
516
# File 'lib/rtlsdr/tcp_client.rb', line 514

def cancel_async
  @streaming = false
end

#closevoid

This method returns an undefined value.

Close the connection

Since:

  • 0.3.0



103
104
105
106
107
108
109
# File 'lib/rtlsdr/tcp_client.rb', line 103

def close
  return unless open?

  @streaming = false
  @socket.close
  @socket = nil
end

#closed?Boolean

Check if connection is closed

Returns:

  • (Boolean)

    true if closed

Since:

  • 0.3.0



114
115
116
# File 'lib/rtlsdr/tcp_client.rb', line 114

def closed?
  !open?
end

#configure(frequency: nil, sample_rate: nil, gain: nil, **options) ⇒ Object

Configuration shortcut

Since:

  • 0.3.0



533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
# File 'lib/rtlsdr/tcp_client.rb', line 533

def configure(frequency: nil, sample_rate: nil, gain: nil, **options)
  self.center_freq = frequency if frequency
  self.sample_rate = sample_rate if sample_rate

  if gain
    manual_gain_mode!
    self.tuner_gain = gain
  end

  options.each do |key, value|
    case key
    when :freq_correction then self.freq_correction = value
    when :agc_mode then self.agc_mode = value
    when :test_mode then self.test_mode = value
    when :bias_tee then self.bias_tee = value
    when :direct_sampling then self.direct_sampling = value
    when :offset_tuning then self.offset_tuning = value
    end
  end

  self
end

#disable_bias_teeBoolean

Disable bias tee

Returns:

  • (Boolean)

    false

Since:

  • 0.3.0



368
369
370
# File 'lib/rtlsdr/tcp_client.rb', line 368

def disable_bias_tee
  self.bias_tee = false
end

#dump_eepromObject

Dump EEPROM (not supported via TCP)

Raises:

Since:

  • 0.3.0



403
404
405
# File 'lib/rtlsdr/tcp_client.rb', line 403

def dump_eeprom
  raise OperationFailedError, "EEPROM access not supported via TCP"
end

#each(samples_per_read: 1024) ⇒ Object

Enumerable interface

Since:

  • 0.3.0



519
520
521
522
523
524
525
526
527
528
529
530
# File 'lib/rtlsdr/tcp_client.rb', line 519

def each(samples_per_read: 1024)
  return enum_for(:each, samples_per_read: samples_per_read) unless block_given?

  loop do
    samples = read_samples(samples_per_read)
    yield samples
  rescue StandardError => e
    break if e.is_a?(Interrupt) || e.is_a?(ConnectionError)

    raise
  end
end

#enable_bias_teeBoolean

Enable bias tee

Returns:

  • (Boolean)

    true

Since:

  • 0.3.0



361
362
363
# File 'lib/rtlsdr/tcp_client.rb', line 361

def enable_bias_tee
  self.bias_tee = true
end

#infoObject

Device info as hash

Since:

  • 0.3.0



557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
# File 'lib/rtlsdr/tcp_client.rb', line 557

def info
  {
    host: @host,
    port: @port,
    name: name,
    usb_strings: usb_strings,
    tuner_type: @tuner_type,
    tuner_name: tuner_name,
    center_freq: center_freq,
    sample_rate: sample_rate,
    tuner_gain: tuner_gain,
    tuner_gains: tuner_gains,
    freq_correction: freq_correction,
    direct_sampling: direct_sampling,
    offset_tuning: offset_tuning
  }
end

#inspectObject

String representation

Since:

  • 0.3.0



576
577
578
579
580
581
582
583
# File 'lib/rtlsdr/tcp_client.rb', line 576

def inspect
  if open?
    "#<RTLSDR::TcpClient:#{object_id.to_s(16)} #{name} tuner=\"#{tuner_name}\" " \
      "freq=#{center_freq}Hz rate=#{sample_rate}Hz>"
  else
    "#<RTLSDR::TcpClient:#{object_id.to_s(16)} #{name} closed>"
  end
end

#manual_gain_mode!Boolean

Enable manual gain mode

Returns:

  • (Boolean)

    true

Since:

  • 0.3.0



233
234
235
# File 'lib/rtlsdr/tcp_client.rb', line 233

def manual_gain_mode!
  self.tuner_gain_mode = true
end

#nameObject

Device information

Since:

  • 0.3.0



119
120
121
# File 'lib/rtlsdr/tcp_client.rb', line 119

def name
  "rtl_tcp://#{@host}:#{@port}"
end

#offset_tuning!Boolean

Enable offset tuning

Returns:

  • (Boolean)

    true

Since:

  • 0.3.0



344
345
346
# File 'lib/rtlsdr/tcp_client.rb', line 344

def offset_tuning!
  self.offset_tuning = true
end

#open?Boolean

Check if connected to server

Returns:

  • (Boolean)

    true if connected

Since:

  • 0.3.0



96
97
98
# File 'lib/rtlsdr/tcp_client.rb', line 96

def open?
  !@socket.nil? && !@socket.closed?
end

#read_async(buffer_count: 15, buffer_length: 262_144) {|Array<Integer>| ... } ⇒ Thread

Read raw IQ data asynchronously

Parameters:

  • buffer_count (Integer) (defaults to: 15)

    Ignored for TCP

  • buffer_length (Integer) (defaults to: 262_144)

    Buffer size in bytes

Yields:

  • (Array<Integer>)

    Block called with each buffer

Returns:

  • (Thread)

    Background reading thread

Raises:

  • (ArgumentError)

Since:

  • 0.3.0



467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
# File 'lib/rtlsdr/tcp_client.rb', line 467

def read_async(buffer_count: 15, buffer_length: 262_144, &block)
  raise ArgumentError, "Block required for async reading" unless block_given?
  raise OperationFailedError, "Already streaming" if streaming?

  _ = buffer_count # unused for TCP
  @streaming = true

  Thread.new do
    while @streaming && open?
      begin
        data = read_sync(buffer_length)
        block.call(data)
      rescue ConnectionError => e
        @streaming = false
        raise e unless e.message.include?("closed")
      rescue StandardError => e
        puts "Error in async callback: #{e.message}"
        @streaming = false
      end
    end
  end
end

#read_eeprom(_offset, _length) ⇒ Object

Read EEPROM (not supported via TCP)

Raises:

Since:

  • 0.3.0



389
390
391
# File 'lib/rtlsdr/tcp_client.rb', line 389

def read_eeprom(_offset, _length)
  raise OperationFailedError, "EEPROM access not supported via TCP"
end

#read_samples(count = 1024) ⇒ Array<Complex>

Read complex samples synchronously

Parameters:

  • count (Integer) (defaults to: 1024)

    Number of complex samples to read

Returns:

  • (Array<Complex>)

    Array of complex samples

Since:

  • 0.3.0



439
440
441
442
443
444
445
446
447
448
449
450
451
452
# File 'lib/rtlsdr/tcp_client.rb', line 439

def read_samples(count = 1024)
  # RTL-SDR outputs 8-bit I/Q samples, so we need 2 bytes per complex sample
  data = read_sync(count * 2)

  # Convert to complex numbers (I + jQ)
  samples = []
  (0...data.length).step(2) do |i|
    i_sample = (data[i] - 128) / 128.0
    q_sample = (data[i + 1] - 128) / 128.0
    samples << Complex(i_sample, q_sample)
  end

  samples
end

#read_samples_async(buffer_count: 15, buffer_length: 262_144) {|Array<Complex>| ... } ⇒ Thread

Read complex samples asynchronously

Parameters:

  • buffer_count (Integer) (defaults to: 15)

    Ignored for TCP

  • buffer_length (Integer) (defaults to: 262_144)

    Buffer size in bytes

Yields:

  • (Array<Complex>)

    Block called with complex samples

Returns:

  • (Thread)

    Background reading thread

Raises:

  • (ArgumentError)

Since:

  • 0.3.0



496
497
498
499
500
501
502
503
504
505
506
507
508
509
# File 'lib/rtlsdr/tcp_client.rb', line 496

def read_samples_async(buffer_count: 15, buffer_length: 262_144, &block)
  raise ArgumentError, "Block required for async reading" unless block_given?

  read_async(buffer_count: buffer_count, buffer_length: buffer_length) do |data|
    samples = []
    (0...data.length).step(2) do |i|
      i_sample = (data[i] - 128) / 128.0
      q_sample = (data[i + 1] - 128) / 128.0
      samples << Complex(i_sample, q_sample)
    end

    block.call(samples)
  end
end

#read_sync(length) ⇒ Array<Integer>

Read raw IQ data synchronously

Parameters:

  • length (Integer)

    Number of bytes to read

Returns:

  • (Array<Integer>)

    Array of 8-bit unsigned integers

Raises:

Since:

  • 0.3.0



418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
# File 'lib/rtlsdr/tcp_client.rb', line 418

def read_sync(length)
  raise ConnectionError, "Not connected" unless open?

  data = +""
  remaining = length

  while remaining.positive?
    chunk = @socket.read(remaining)
    raise ConnectionError, "Connection closed by server" if chunk.nil? || chunk.empty?

    data << chunk
    remaining -= chunk.bytesize
  end

  data.unpack("C*")
end

#reset_bufferObject

Buffer reset (no-op for TCP)

Since:

  • 0.3.0



408
409
410
411
# File 'lib/rtlsdr/tcp_client.rb', line 408

def reset_buffer
  # rtl_tcp doesn't have a reset buffer command
  # Data is continuously streamed
end

#set_bias_tee_gpio(_gpio, enabled) ⇒ Object

Bias tee GPIO not supported via TCP

Since:

  • 0.3.0



380
381
382
# File 'lib/rtlsdr/tcp_client.rb', line 380

def set_bias_tee_gpio(_gpio, enabled)
  self.bias_tee = enabled
end

#set_tuner_if_gain(stage, gain) ⇒ Integer

Set IF gain for specific stage

Parameters:

  • stage (Integer)

    IF stage number

  • gain (Integer)

    Gain value in tenths of dB

Returns:

  • (Integer)

    The gain value that was set

Since:

  • 0.3.0



249
250
251
252
253
# File 'lib/rtlsdr/tcp_client.rb', line 249

def set_tuner_if_gain(stage, gain)
  param = (stage << 16) | (gain & 0xFFFF)
  send_command(CMD_SET_IF_GAIN, param)
  gain
end

#set_xtal_freq(rtl_freq, tuner_freq) ⇒ Object

Crystal oscillator frequencies (not fully supported via TCP)

Since:

  • 0.3.0



181
182
183
184
185
# File 'lib/rtlsdr/tcp_client.rb', line 181

def set_xtal_freq(rtl_freq, tuner_freq)
  send_command(CMD_SET_RTL_XTAL, rtl_freq)
  send_command(CMD_SET_TUNER_XTAL, tuner_freq)
  [rtl_freq, tuner_freq]
end

#streaming?Boolean

Check if streaming

Returns:

  • (Boolean)

    true if streaming

Since:

  • 0.3.0



457
458
459
# File 'lib/rtlsdr/tcp_client.rb', line 457

def streaming?
  @streaming
end

#test_mode!Boolean

Enable test mode

Returns:

  • (Boolean)

    true

Since:

  • 0.3.0



294
295
296
# File 'lib/rtlsdr/tcp_client.rb', line 294

def test_mode!
  self.test_mode = true
end

#test_mode=(enabled) ⇒ Object

Set test mode

Parameters:

  • enabled (Boolean)

    true to enable test mode

Since:

  • 0.3.0



285
286
287
288
289
# File 'lib/rtlsdr/tcp_client.rb', line 285

def test_mode=(enabled)
  mode = enabled ? 1 : 0
  send_command(CMD_SET_TEST_MODE, mode)
  @test_mode = enabled
end

#tuner_bandwidth=(_bw) ⇒ Object

Set tuner bandwidth (not supported via rtl_tcp)

Parameters:

  • _bw (Integer)

    Bandwidth in Hz (ignored)

Since:

  • 0.3.0



258
259
260
# File 'lib/rtlsdr/tcp_client.rb', line 258

def tuner_bandwidth=(_bw)
  # rtl_tcp doesn't support bandwidth command
end

#tuner_gain_mode=(manual) ⇒ Object

Set gain mode (manual or automatic)

Parameters:

  • manual (Boolean)

    true for manual, false for automatic

Since:

  • 0.3.0



224
225
226
227
228
# File 'lib/rtlsdr/tcp_client.rb', line 224

def tuner_gain_mode=(manual)
  mode = manual ? 1 : 0
  send_command(CMD_SET_GAIN_MODE, mode)
  @gain_mode_manual = manual
end

#tuner_gainsArray<Integer>

Get available tuner gains (not queryable via TCP, returns common values)

Returns:

  • (Array<Integer>)

    Common R820T gain values in tenths of dB

Since:

  • 0.3.0



199
200
201
202
203
# File 'lib/rtlsdr/tcp_client.rb', line 199

def tuner_gains
  # Return common R820T gains as a reasonable default
  [0, 9, 14, 27, 37, 77, 87, 125, 144, 157, 166, 197, 207, 229, 254,
   280, 297, 328, 338, 364, 372, 386, 402, 421, 434, 439, 445, 480, 496]
end

#tuner_nameString

Get human-readable tuner name

Returns:

  • (String)

    Tuner name based on type code

Since:

  • 0.3.0



137
138
139
140
141
142
143
144
145
146
147
# File 'lib/rtlsdr/tcp_client.rb', line 137

def tuner_name
  case @tuner_type
  when 1 then "Elonics E4000"
  when 2 then "Fitipower FC0012"
  when 3 then "Fitipower FC0013"
  when 4 then "FCI FC2580"
  when 5 then "Rafael Micro R820T"
  when 6 then "Rafael Micro R828D"
  else "Unknown"
  end
end

#usb_stringsHash

Get USB device strings (not available via TCP)

Returns:

  • (Hash)

    Hash with placeholder values

Since:

  • 0.3.0



126
127
128
129
130
131
132
# File 'lib/rtlsdr/tcp_client.rb', line 126

def usb_strings
  {
    manufacturer: "rtl_tcp",
    product: "Remote RTL-SDR",
    serial: "#{@host}:#{@port}"
  }
end

#write_eeprom(_data, _offset) ⇒ Object

Write EEPROM (not supported via TCP)

Raises:

Since:

  • 0.3.0



396
397
398
# File 'lib/rtlsdr/tcp_client.rb', line 396

def write_eeprom(_data, _offset)
  raise OperationFailedError, "EEPROM access not supported via TCP"
end

#xtal_freqArray<Integer>

Get crystal frequencies (returns zeros - not readable via TCP)

Returns:

  • (Array<Integer>)

    Array of [0, 0]

Since:

  • 0.3.0



190
191
192
# File 'lib/rtlsdr/tcp_client.rb', line 190

def xtal_freq
  [0, 0]
end