Class: HDHomeRun::Tuner

Inherits:
Device
  • Object
show all
Extended by:
Forwardable
Includes:
FFI::HDHomeRun
Defined in:
lib/ffi-hdhomerun.rb

Overview

HDHomeRun tuner wrapper.

Basic usage:

# Get a tuner instance for the first tuner on the network.
tuner = HDHomeRun::Tuner.new(:id => "FFFFFFFF", :tuner => 0)

# Set the channel and program
tuner.channel = 20
tuner.program = 3

# Open a file to write the video stream to
File.open("capture.ts", "w") do |output|
  puts "Capturing (^C to stop):"

  # Read the video stream from the tuner
  tuner.capture do |video_data|

    # write the video data to the output file
    output.write(video_data)

    # Display some sort of feedback to show we're capturing.
    STDOUT.write(video_data.size > 0 ? "." : "?")
    STDOUT.flush
  end
end

Constant Summary

Constants included from FFI::HDHomeRun

FFI::HDHomeRun::DEVICE_ID_WILDCARD, FFI::HDHomeRun::DEVICE_TYPE_TUNER, FFI::HDHomeRun::VIDEO_DATA_BUFFER_SIZE_1S

Instance Attribute Summary collapse

Attributes inherited from Device

#id

Instance Method Summary collapse

Methods inherited from Device

#get, #set, #tuner_count, #tuners

Constructor Details

#initialize(p = {}) ⇒ Tuner

Initialize new hdhomerun tuner

Arguments:

:id

HDHomeRun id (default: “FFFFFFFF”)

:tuner

Tuner number (default: “/tuner0”)

:key

lockkey for tuner, see lock (default: nil)

Create a tuner instance for the first tuner on any HDHomeRun box on the network:

tuner => HDHomeRun::Tuner.new

Create a tuner instance for the second tuner on the HDHomeRun box with id FE92EBD0

tuner = HDHomeRun::Tuner.new(:id => "FE92EBD0", :tuner => 1)

Raises:



239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
# File 'lib/ffi-hdhomerun.rb', line 239

def initialize(p={})
  super(p)
  @tuner = p[:tuner] || 0
  @tuner = "/tuner%d" % @tuner if @tuner.is_a? Fixnum
  self.key = p[:key] if p[:key]

  # Verify the format of the tuner argument
  raise InvalidTunerError, "invalid tuner: %s" % @tuner \
    if set_tuner_from_str(@hd, @tuner) <= 0

  # Final check to see if the tuner exists on our device.
  begin
    get_tuner("status")
  rescue UnknownVariableError => e
    raise InvalidTunerError, "invalid tuner: %s, %s" % [@tuner, e]
  end
end

Instance Attribute Details

#tunerObject (readonly)

Returns the value of attribute tuner.



222
223
224
# File 'lib/ffi-hdhomerun.rb', line 222

def tuner
  @tuner
end

Instance Method Details

#capture(p = {}, &block) ⇒ Object

Capture video data from the tuner.

As data is received from the tuner, that data will be yielded to the block provided. If no data has been received from the tuner in over a second, capture will yield a string of length zero to the block.

If the block returns [false], the tuner will stop capturing and return.

Capture from the tuner, but abort if we see no data:

tuner.capture do |buf|
  break false if buf.empty?
end

Raises:

  • (ArgumentError)


298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
# File 'lib/ffi-hdhomerun.rb', line 298

def capture(p={}, &block)
  raise ArgumentError, "no block" unless block_given?
  raise RuntimeError, "unable to start stream" \
    if stream_start(@hd) <= 0

  max_delay = (p[:delay] || 0.064).to_f
  last_yield = 0

  begin
    len_ptr = FFI::MemoryPointer.new :ulong

    while true do
      start = Time.now.to_f
      data  = stream_recv(@hd, VIDEO_DATA_BUFFER_SIZE_1S, len_ptr)

      if !data.null? or start - last_yield > 1.0
        last_yield = start

        len = len_ptr.read_ulong
        buf = data.read_string(len) if len > 0
        buf ||= ""
        yield(buf) or break
      end

      delay = max_delay - (Time.now.to_f - start)
      sleep delay if delay > 0
    end
  ensure
    stream_stop(@hd)
  end
end

#key=(key) ⇒ Object

Specify the key to use when communicating with this tuner.



418
419
420
# File 'lib/ffi-hdhomerun.rb', line 418

def key=(key)
  lockkey_use_value(@hd, key.to_i)
end

#lock(k = nil) ⇒ Object

Lock the tuner. Optionally a key can be provided to allow locking across non-persistent connections.

:k:

optional key for non-persistent connections

Examples:

Locking with a persistent connection:

t = Tuner.new(:id => 0xFEFEFEFE, :tuner => 0)
t.lock            # => true
t.channel = 3     # => 3
# Create a second instance of the tuner and try to change
# the channel.
t2 = Tuner.new(:id => 0xFEFEFEFE, :tuner => 0)
t2.channel = 4    # => TunerLockedError exception
# first tuner instance releases lock
t.unlock          # => true
# Now second tuner can make changes.
t2.channel = 4    # => 4

Locking across non-persistent connections:

# Grab a tuner and lock it.
t = Tuner.new(:id => 0xFEFEFEFE, :tuner => 0)
t.lock 0xFFFF     # => true
t.channel = 3     # => 3
t = nil           # => nil

# Create a new instance referencing the same tuner, cannot
# set tuner variables without setting the key first.
t2 = Tuner.new(:id => 0xFEFEFEFE, :tuner => 0)
t2.channel = 4    # => TunerLockedError exception
t2.key = 0xFFFF   # => true
t2.channel = 4    # => 4
t2 = nil          # => nil

# tuner key can be passed in instansiation arguments.
t3 = Tuner.new(:id => 0xFEFEFEFE, :tuner => 0, :key => 0xFFFF)
t3.channel = 5    # => 5

# Invalid keys (or expired keys) result in a LockExpiredError
t3.key = 0x1234   # => true
t3.channel = 6    # => LockExpiredError exception
t3 = nil          # => nil


376
377
378
379
380
381
382
383
# File 'lib/ffi-hdhomerun.rb', line 376

def lock(k = nil)
  # If no key was provided, generate a random key for this
  # session.
  k ||= rand(0xFFFFFFFF)
  set_tuner("lockkey", k)
  self.key = k
  true
end

#lock_ownerObject

Return the ip address that holds the lock on the tuner



428
429
430
# File 'lib/ffi-hdhomerun.rb', line 428

def lock_owner
  get_tuner("lockkey")
end

#locked?Boolean

check to see if the tuner is locked

Returns:

  • (Boolean)


423
424
425
# File 'lib/ffi-hdhomerun.rb', line 423

def locked?
  get_tuner("lockkey") != "none"
end

#statsObject

Get the capture statistics for the tuner.

Returns: FFI::HDHomeRun::Stats



278
279
280
281
282
283
# File 'lib/ffi-hdhomerun.rb', line 278

def stats
  ptr = FFI::MemoryPointer.new Stats
  out = Stats.new ptr;
  get_video_stats(@hd, out)
  out
end

#to_sObject



432
433
434
435
# File 'lib/ffi-hdhomerun.rb', line 432

def to_s
  "<%s:0x%08x @id=%08X, @tuner=%s>" % 
    [ self.class, object_id, @id, @tuner ]
end

#unlock(force = false) ⇒ Object

Unlock the tuner. Requires you to already have the correct key set or use the [:force:] argument.

Example:

t = Tuner.new(:id => 0xFEFEFEFE, :tuner => 0)
t2 = Tuner.new(:id => 0xFEFEFEFE, :tuner => 0)

# Lock the tuner and try to unlock it without the key
t.lock            # => true
t2.unlock         # => TunerLockedError exception
t.locked?         # => true

# force the tuner to unlock
t2.unlock :force  # => true
t.locked?         # => false

Raises:



401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
# File 'lib/ffi-hdhomerun.rb', line 401

def unlock(force=false)
  # FIXME uncommenting the following line causes the tests to core
  # dump.
  # return true unless locked?
  set_tuner("lockkey", force != false ? "force" : "none")
  self.key = 0

  # Strange behavior here, setting the lockkey to "none" without
  # the correct lockkey does not generate an error.  Instead the
  # new value is ignored by the tuner.  So we have to check here
  # to see if the tuner is still locked here to tell if our unlock
  # was successful.
  raise TunerLockedError if locked?
  true
end