ffi-hdhomerun 0.4 → 0.5

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.
data/lib/ffi-hdhomerun.rb CHANGED
@@ -5,12 +5,18 @@ require 'pry'
5
5
  # Documentation can be found under HDHomeRun::Tuner
6
6
  #
7
7
  module HDHomeRun
8
- VERSION = "0.4"
8
+ VERSION = "0.5"
9
9
  ERROR_STRING_UNKNOWN_VARIABLE = "ERROR: unknown getset variable"
10
+ ERROR_REGEX_TUNER_LOCKED = /^ERROR: resource locked by /
11
+ ERROR_STRING_TUNER_LOCK_EXPIRED = "ERROR: lock no longer held"
10
12
  MAX_TUNERS = 8
11
13
 
14
+ class InvalidDeviceIdError < Exception; end
12
15
  class ConnectionError < Exception; end
16
+ class InvalidTunerError < Exception; end
13
17
  class UnknownVariableError < Exception; end
18
+ class TunerLockedError < Exception; end
19
+ class LockExpiredError < Exception; end
14
20
 
15
21
  #
16
22
  # Discover tuners on the network.
@@ -24,7 +30,8 @@ module HDHomeRun
24
30
  # :tuner_count -- number of tuners on the device (int)
25
31
  #
26
32
  def self.discover
27
- results_ptr = FFI::MemoryPointer.new(FFI::HDHomeRun::Device, 64)
33
+ results_ptr = FFI::MemoryPointer.new(
34
+ FFI::HDHomeRun::DiscoverDevice, 64)
28
35
  count = FFI::HDHomeRun::discover_find_devices_custom(
29
36
  FFI::HDHomeRun::DEVICE_ID_WILDCARD,
30
37
  FFI::HDHomeRun::DEVICE_TYPE_TUNER,
@@ -38,7 +45,7 @@ module HDHomeRun
38
45
  (0..count-1).map do |i|
39
46
 
40
47
  # construct our hash
41
- p = FFI::HDHomeRun::Device.new(results_ptr[i])
48
+ p = FFI::HDHomeRun::DiscoverDevice.new(results_ptr[i])
42
49
  out = { :id => "%08X" % p.device_id,
43
50
  :type => p.device_type,
44
51
  :ip_addr => p.ip_addr }
@@ -74,9 +81,11 @@ module HDHomeRun
74
81
  p[:id] = "%08X" % p[:id] if p[:id].is_a? Fixnum
75
82
 
76
83
  @hd = create_from_str(p[:id].to_s, nil)
77
- raise ArgumentError, "Invalid device id: %s" % p[:id] if @hd.null?
84
+ raise InvalidDeviceIdError, "Invalid device id: %s" % p[:id] \
85
+ if @hd.null?
86
+ @hd = FFI::HDHomeRun::Device.new(@hd)
78
87
  @id = get_device_id_requested(@hd)
79
- raise ArgumentError, "Invalid device id: %08X" % id \
88
+ raise InvalidDeviceIdError, "Invalid device id: %08X" % @id \
80
89
  unless validate_device_id(@id)
81
90
  raise ConnectionError, "Unable to connect to device" \
82
91
  unless get_model_str(@hd)
@@ -104,7 +113,7 @@ module HDHomeRun
104
113
  error = error.read_string
105
114
  raise UnknownVariableError, "unknown variable '%s'" % key if
106
115
  error == ERROR_STRING_UNKNOWN_VARIABLE
107
-
116
+
108
117
  # otherwise, it's a generic runtime error.
109
118
  raise RuntimeError, error
110
119
  end
@@ -126,9 +135,14 @@ module HDHomeRun
126
135
 
127
136
  # Throw a useful exception if this get was for an unknown variable.
128
137
  error = error.read_string
129
- raise UnknownVariableError, "unknown variable '%s'" % key if
130
- error == ERROR_STRING_UNKNOWN_VARIABLE
131
-
138
+ raise UnknownVariableError, "unknown variable '%s'" % key \
139
+ if error == ERROR_STRING_UNKNOWN_VARIABLE
140
+
141
+ # Check for locking exceptions
142
+ raise TunerLockedError, error if error =~ ERROR_REGEX_TUNER_LOCKED
143
+ raise LockExpiredError, error \
144
+ if error == ERROR_STRING_TUNER_LOCK_EXPIRED
145
+
132
146
  # otherwise, it's a generic runtime error.
133
147
  raise RuntimeError, error
134
148
  end
@@ -200,6 +214,7 @@ module HDHomeRun
200
214
  # Arguments:
201
215
  # [:id] HDHomeRun id (default: "FFFFFFFF")
202
216
  # [:tuner] Tuner number (default: "/tuner0")
217
+ # [:key] lockkey for tuner, see lock (default: nil)
203
218
  #
204
219
  # Create a tuner instance for the first tuner on any HDHomeRun box
205
220
  # on the network:
@@ -213,9 +228,17 @@ module HDHomeRun
213
228
  super(p)
214
229
  @tuner = p[:tuner] || 0
215
230
  @tuner = "/tuner%d" % @tuner if @tuner.is_a? Fixnum
231
+ self.key = p[:key] if p[:key]
232
+
233
+ # Verify the format of the tuner argument
234
+ raise InvalidTunerError, "invalid tuner: %s" % @tuner \
235
+ if set_tuner_from_str(@hd, @tuner) <= 0
216
236
 
217
- if set_tuner_from_str(@hd, @tuner) <= 0
218
- raise RuntimeError, "invalid tuner: %s" % @tuner
237
+ # Final check to see if the tuner exists on our device.
238
+ begin
239
+ get_tuner("status")
240
+ rescue UnknownVariableError => e
241
+ raise InvalidTunerError, "invalid tuner: %s, %s" % [@tuner, e]
219
242
  end
220
243
  end
221
244
 
@@ -292,6 +315,108 @@ module HDHomeRun
292
315
  end
293
316
  end
294
317
 
318
+ # Lock the tuner. Optionally a key can be provided to allow
319
+ # locking across non-persistent connections.
320
+ #
321
+ # [:k:] optional key for non-persistent connections
322
+ #
323
+ # Examples:
324
+ #
325
+ # Locking with a persistent connection:
326
+ #
327
+ # t = Tuner.new(:id => 0xFEFEFEFE, :tuner => 0)
328
+ # t.lock # => true
329
+ # t.channel = 3 # => 3
330
+ # # Create a second instance of the tuner and try to change
331
+ # # the channel.
332
+ # t2 = Tuner.new(:id => 0xFEFEFEFE, :tuner => 0)
333
+ # t2.channel = 4 # => TunerLockedError exception
334
+ # # first tuner instance releases lock
335
+ # t.unlock # => true
336
+ # # Now second tuner can make changes.
337
+ # t2.channel = 4 # => 4
338
+ #
339
+ # Locking across non-persistent connections:
340
+ #
341
+ # # Grab a tuner and lock it.
342
+ # t = Tuner.new(:id => 0xFEFEFEFE, :tuner => 0)
343
+ # t.lock 0xFFFF # => true
344
+ # t.channel = 3 # => 3
345
+ # t = nil # => nil
346
+ #
347
+ # # Create a new instance referencing the same tuner, cannot
348
+ # # set tuner variables without setting the key first.
349
+ # t2 = Tuner.new(:id => 0xFEFEFEFE, :tuner => 0)
350
+ # t2.channel = 4 # => TunerLockedError exception
351
+ # t2.key = 0xFFFF # => true
352
+ # t2.channel = 4 # => 4
353
+ # t2 = nil # => nil
354
+ #
355
+ # # tuner key can be passed in instansiation arguments.
356
+ # t3 = Tuner.new(:id => 0xFEFEFEFE, :tuner => 0, :key => 0xFFFF)
357
+ # t3.channel = 5 # => 5
358
+ #
359
+ # # Invalid keys (or expired keys) result in a LockExpiredError
360
+ # t3.key = 0x1234 # => true
361
+ # t3.channel = 6 # => LockExpiredError exception
362
+ # t3 = nil # => nil
363
+ #
364
+ def lock(k = nil)
365
+ # If no key was provided, generate a random key for this
366
+ # session.
367
+ k ||= rand(0xFFFFFFFF)
368
+ set_tuner("lockkey", k)
369
+ self.key = k
370
+ true
371
+ end
372
+
373
+ # Unlock the tuner. Requires you to already have the correct
374
+ # key set or use the [:force:] argument.
375
+ #
376
+ # Example:
377
+ # t = Tuner.new(:id => 0xFEFEFEFE, :tuner => 0)
378
+ # t2 = Tuner.new(:id => 0xFEFEFEFE, :tuner => 0)
379
+ #
380
+ # # Lock the tuner and try to unlock it without the key
381
+ # t.lock # => true
382
+ # t2.unlock # => TunerLockedError exception
383
+ # t.locked? # => true
384
+ #
385
+ # # force the tuner to unlock
386
+ # t2.unlock :force # => true
387
+ # t.locked? # => false
388
+ #
389
+ def unlock(force=false)
390
+ # FIXME uncommenting the following line causes the tests to core
391
+ # dump.
392
+ # return true unless locked?
393
+ set_tuner("lockkey", force != false ? "force" : "none")
394
+ self.key = 0
395
+
396
+ # Strange behavior here, setting the lockkey to "none" without
397
+ # the correct lockkey does not generate an error. Instead the
398
+ # new value is ignored by the tuner. So we have to check here
399
+ # to see if the tuner is still locked here to tell if our unlock
400
+ # was successful.
401
+ raise TunerLockedError if locked?
402
+ true
403
+ end
404
+
405
+ # Specify the key to use when communicating with this tuner.
406
+ def key=(key)
407
+ lockkey_use_value(@hd, key.to_i)
408
+ end
409
+
410
+ # check to see if the tuner is locked
411
+ def locked?
412
+ get_tuner("lockkey") != "none"
413
+ end
414
+
415
+ # Return the ip address that holds the lock on the tuner
416
+ def lock_owner
417
+ get_tuner("lockkey")
418
+ end
419
+
295
420
  def to_s
296
421
  "<%s:0x%08x @id=%08X, @tuner=%s>" %
297
422
  [ self.class, object_id, @id, @tuner ]
data/lib/ffi/hdhomerun.rb CHANGED
@@ -47,6 +47,9 @@ module FFI # :nodoc:
47
47
  :hdhomerun_discover_find_devices_custom,
48
48
  [ :uint32, :uint32, :uint32, :pointer, :int ],
49
49
  :int
50
+ attach_function :lockkey_use_value,
51
+ :hdhomerun_device_tuner_lockkey_use_value,
52
+ [:pointer, :ulong], :void
50
53
 
51
54
  # HDHomeRun capture statistics for a given tuner
52
55
  #
@@ -71,6 +74,25 @@ module FFI # :nodoc:
71
74
  end
72
75
  end
73
76
 
77
+ class Device < FFI::Struct
78
+ layout :cs, :pointer,
79
+ :vs, :pointer,
80
+ :dbg, :pointer,
81
+ :scan, :pointer,
82
+ :multicast_ip, :uint32,
83
+ :multicast_port, :uint16,
84
+ :device_id, :uint,
85
+ :lockkey, :uint32,
86
+ :name, [:char, 32],
87
+ :model, [:char, 32]
88
+
89
+ members.each do |name|
90
+ define_method name.to_s do
91
+ send(:[], name.to_sym)
92
+ end
93
+ end
94
+ end
95
+
74
96
  # Discover device structure
75
97
  #
76
98
  # Silicon dust changed this structure at some point between
@@ -82,7 +104,7 @@ module FFI # :nodoc:
82
104
  # FFI_HDHOMERUN_OLD_DEVICE_STRUCT environment variable has been
83
105
  # set.
84
106
  #
85
- class Device < FFI::Struct
107
+ class DiscoverDevice < FFI::Struct
86
108
  if ENV['FFI_HDHOMERUN_OLD_DEVICE_STRUCT']
87
109
  layout :ip_addr, :uint32,
88
110
  :device_type, :uint32,
@@ -19,7 +19,7 @@ describe HDHomeRun::Device do
19
19
 
20
20
  it "throws exception on invalid name" do
21
21
  lambda { HDHomeRun::Device.new("WOOF WOOF, I'm a puppy!") } \
22
- .should raise_error(ArgumentError)
22
+ .should raise_error(HDHomeRun::InvalidDeviceIdError)
23
23
  end
24
24
 
25
25
  it "throws exception on unreachable device" do
@@ -74,6 +74,13 @@ describe HDHomeRun::Tuner do
74
74
  "to a channel that comes in clearly on your tuner" unless @channel
75
75
  end
76
76
 
77
+ it "throws exception for invalid tuners" do
78
+ lambda { HDHomeRun::Tuner.new(:tuner => "woof") } \
79
+ .should raise_error(HDHomeRun::InvalidTunerError)
80
+ lambda { HDHomeRun::Tuner.new(:tuner => 9999) } \
81
+ .should raise_error(HDHomeRun::InvalidTunerError)
82
+ end
83
+
77
84
  it "can set and get the channel" do
78
85
  @tuner.channel = "none"
79
86
  @tuner.channel.should eq("none")
@@ -100,5 +107,78 @@ describe HDHomeRun::Tuner do
100
107
 
101
108
  bytes.size.should be > 0
102
109
  end
110
+
111
+ it "can be locked non-persistently" do
112
+ tuner2 = HDHomeRun::Tuner.new(:id => @tuner.id, :tuner => @tuner.tuner)
113
+ @tuner.lock.should be true
114
+ @tuner.channel = 33
115
+
116
+ lambda { tuner2.channel = 3 } \
117
+ .should raise_error(HDHomeRun::TunerLockedError)
118
+
119
+ @tuner.unlock
120
+ end
121
+
122
+ it "can be locked persistently" do
123
+ t = HDHomeRun::Tuner.new(:id => @tuner.id, :tuner => @tuner.tuner)
124
+ t.lock(0xFACE).should be true
125
+ t.key = 0xFACE
126
+ t.channel = 11
127
+
128
+ lambda { @tuner.channel = 3 } \
129
+ .should raise_error(HDHomeRun::TunerLockedError)
130
+
131
+ t = nil
132
+ t = HDHomeRun::Tuner.new(:id => @tuner.id, :tuner => @tuner.tuner)
133
+ t.key = 0xFACE
134
+
135
+ t.channel = 7
136
+
137
+ lambda { @tuner.channel = 3 } \
138
+ .should raise_error(HDHomeRun::TunerLockedError)
139
+
140
+ t.unlock
141
+
142
+ @tuner.channel = 3
143
+ end
144
+
145
+ it "cannot be double locked" do
146
+ t = HDHomeRun::Tuner.new(:id => @tuner.id, :tuner => @tuner.tuner)
147
+ @tuner.lock
148
+ lambda { t.lock(0x3333) } \
149
+ .should raise_error(HDHomeRun::TunerLockedError)
150
+ @tuner.unlock
151
+ end
152
+
153
+ it "rejects invalid keys" do
154
+ @tuner.unlock(:force)
155
+ @tuner.key = 0xFEEDFACE
156
+ lambda { @tuner.channel = 3 } \
157
+ .should raise_error(HDHomeRun::LockExpiredError)
158
+ end
159
+
160
+ it "can check to see if the tuner is locked" do
161
+ @tuner.locked?.should be false
162
+ t = HDHomeRun::Tuner.new(:id => @tuner.id, :tuner => @tuner.tuner)
163
+ t.lock
164
+ @tuner.locked?.should be true
165
+ t.unlock
166
+ end
167
+
168
+ it "cannot unlock without the correct key" do
169
+ t = HDHomeRun::Tuner.new(:id => @tuner.id, :tuner => @tuner.tuner)
170
+ @tuner.lock
171
+ lambda { t.unlock } \
172
+ .should raise_error(HDHomeRun::TunerLockedError)
173
+ @tuner.locked?.should be true
174
+ @tuner.unlock
175
+ end
176
+
177
+ it "can force a tuner to unlock" do
178
+ t = HDHomeRun::Tuner.new(:id => @tuner.id, :tuner => @tuner.tuner)
179
+ @tuner.lock
180
+ t.unlock(:force)
181
+ @tuner.locked?.should be false
182
+ end
103
183
  end
104
184
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ffi-hdhomerun
3
3
  version: !ruby/object:Gem::Version
4
- version: '0.4'
4
+ version: '0.5'
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-07-07 00:00:00.000000000 Z
12
+ date: 2012-09-15 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: ffi