seriamp 0.2.1 → 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 987bf9939c33251dcb4b509a4a793d4b2b832f45fa2089e924f1960271eb7a70
4
- data.tar.gz: 6e19b0e1a0ba95fed263f12668830e3b3b58b0e744d2031f2920d4eb63b63f0b
3
+ metadata.gz: bdacf1b53664adff670eaa918cdc44aa60ba20660249456b916a26c30f69ae08
4
+ data.tar.gz: 7f2cebc5518a380c4db21155735666481eef942d75f9ece04952ee1fac45c6e8
5
5
  SHA512:
6
- metadata.gz: 4ad4de970b57453c39b57c37407579214196ecdd9db3f86be4a7c189d0a7aae697bd0d98c0866cc045ad915e611e9c520bcbfd8f5c10dea322086c35f6c13537
7
- data.tar.gz: 3fef6d4e749ee56b1b590cd7c0044f2e15042129de38ae6d7a9600730029a57573246af2432c981dc1d1f9355a01677afcbb1ab3b2cc5f2442bdc68466f34165
6
+ metadata.gz: 301d7fb20466d009da6f9f9038da12b07e6d20491ada5d8b99ea03c96d09f1fc83c5c50d4d96ba900f98fd0b0da74a1c29da5f7afbe4d5c08c50832b06f05a8f
7
+ data.tar.gz: 6595c0244d77380aa768c41227d8f521ee4107a28d08fb55165500a0bc3abc6447db61afe0565472c14fc09a3795b0e8036f33b499a2b9a7cba794c857b15697
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- seriamp (0.2.1)
4
+ seriamp (0.2.2)
5
5
  serialport (~> 1.3)
6
6
 
7
7
  GEM
data/README.md CHANGED
@@ -20,17 +20,76 @@ that requires a straight cable and has a female terminal (such as the
20
20
  Denon AVR-2308CI). For other receivers a gender changer or a cable is
21
21
  necessary.
22
22
 
23
+ ### Serial Adapter & Cable Considerations
24
+
25
+ The adapters and cables have either screws or nuts on them to secure
26
+ the connections together. Ideally one side of the connection should
27
+ have a screw and the other a nut. If both sides have screws, the
28
+ connection would work but would be loose. If both sides have nuts,
29
+ the connection won't work as the connectors will physically not able to
30
+ meet.
31
+
32
+ Cables and adapters come with a variety of combinations of male/female
33
+ connectors and nuts/screws, therefore this is a good area
34
+ to pay attention to when purchasing the hardware.
35
+
36
+ USB to serial adapters:
37
+
38
+ - [Male connector with nuts](https://www.amazon.com/gp/product/B00IDSM6BW)
39
+ - [Male connector with screws](https://www.amazon.com/gp/product/B0759HSLP1),
40
+ also [this](https://www.amazon.com/gp/product/B017D51ZRQ) and
41
+ [this](https://www.amazon.com/gp/product/B00ZHP2NN0)
42
+
43
+ Serial cables:
44
+
45
+ - [Male/screw to female/screw, null modem](https://www.amazon.com/gp/product/B00CEMGMMM)
46
+ - [Male/screw to male/screw, null modem](https://www.amazon.com/gp/product/B00006B8BJ)
47
+
48
+ Mini adapters / gender changers:
49
+
50
+ - [Null modem male/screw to male/nut](https://www.ebay.com/itm/225083094726)
51
+ - [Null modem male/screw to female/nut](https://www.ebay.com/itm/123731343721)
52
+ - [Null modem male/screw to female/screw](https://www.ebay.com/itm/255420011438)
53
+ - [Null modem female/screw to female/nut](https://www.ebay.com/itm/123732427356)
54
+ - [Null modem female/screw to female/screw](https://www.ebay.com/itm/333767424713)
55
+ - [Straight female/screw to female/screw](https://www.ebay.com/itm/313578863735)
56
+
57
+ The mini adapters/gender changers are generally cheaper than serial cables,
58
+ but cables can be sourced for quite cheap as well. For example, as of
59
+ this writing, the adapters are sold on eBay for about $3.50 and cables can
60
+ be bought on Amazon for about $5.50. When using adapters instead of or
61
+ in addition to cables, keep in mind that the adapters, being rigidly attached
62
+ to the device, will protrude backwards and in particular if a receiver or
63
+ amplifier is already positioned close to a wall (or the rear wall of a cabinet),
64
+ adding an adapter to the receiver/amplifier may require moving the device
65
+ further away from the wall in order to fit the adapter and the serial
66
+ cable connector.
67
+
23
68
  ### Sonance Sonamp 875D / 875D MK II
24
69
 
25
70
  - 3-pin cable should be sufficient according to manual
26
71
  - Null-modem cable required
27
- - Receiver socket is female
72
+ - Receiver socket is female with nuts
73
+
74
+ Connection options:
75
+
76
+ - PC with serial port (male) <-> null-modem cable female to male <->
77
+ receiver
78
+ - USB-serial adapter (male) <-> null-modem male to female adapter <->
79
+ receiver
28
80
 
29
81
  ### Yamaha RX-V**00
30
82
 
31
83
  - 5-pin cable required (with RTS pin connected)
32
84
  - Null-modem cable required
33
- - Receiver socket is male
85
+ - Receiver socket is male with nuts
86
+
87
+ Connection options:
88
+
89
+ - PC with serial port (male) <-> null-modem cable female to female <->
90
+ receiver
91
+ - USB-serial adapter (male) <-> null-modem female to female adapter <->
92
+ receiver
34
93
 
35
94
  The following table shows which Yamaha receivers have RS-232 connector
36
95
  and which do not:
@@ -86,6 +145,12 @@ either a straight through female to male serial cable or removing the nuts
86
145
  from one of the ends (the USB to serial adapter is the cheaper device,
87
146
  I modify the adapters rather than the receivers/amplifiers).
88
147
 
148
+ Connection options:
149
+
150
+ - PC with serial port (male) <-> straight cable female to male <->
151
+ receiver
152
+ - USB-serial adapter (male) <-> receiver
153
+
89
154
  ## Protocol Notes
90
155
 
91
156
  ### RX-V1500 Power Values
@@ -7,7 +7,7 @@ module Seriamp
7
7
 
8
8
  DEFAULT_DEVICE_GLOB = '/dev/ttyUSB*'
9
9
 
10
- module_function def detect_device(mod, *patterns, logger: nil)
10
+ module_function def detect_device(mod, *patterns, logger: nil, timeout: nil)
11
11
  if patterns.empty?
12
12
  patterns = [DEFAULT_DEVICE_GLOB]
13
13
  end
@@ -15,13 +15,13 @@ module Seriamp
15
15
  Dir.glob(pattern)
16
16
  end.flatten.uniq
17
17
  queue = Queue.new
18
- timeout = mod.const_get(:RS232_TIMEOUT)
18
+ timeout ||= mod.const_get(:DEFAULT_RS232_TIMEOUT)
19
19
  client_cls = mod.const_get(:Client)
20
20
  threads = devices.map do |device|
21
21
  Thread.new do
22
22
  Timeout.timeout(timeout * 2, CommunicationTimeout) do
23
23
  logger&.debug("Trying #{device}")
24
- client_cls.new(device: device, logger: logger, retries: false).present?
24
+ client_cls.new(device: device, logger: logger, retries: false, timeout: timeout).present?
25
25
  logger&.debug("Found #{mod} device at #{device}")
26
26
  queue << device
27
27
  end
data/lib/seriamp/error.rb CHANGED
@@ -7,4 +7,6 @@ module Seriamp
7
7
  class UnexpectedResponse < Error; end
8
8
  class HandshakeFailure < UnexpectedResponse; end
9
9
  class CommunicationTimeout < Error; end
10
+
11
+ class InvalidOnOffValue < ArgumentError; end
10
12
  end
@@ -8,12 +8,14 @@ require 'seriamp/integra/protocol/methods'
8
8
  module Seriamp
9
9
  module Integra
10
10
 
11
- RS232_TIMEOUT = 0.25
11
+ DEFAULT_RS232_TIMEOUT = 0.25
12
12
 
13
13
  class Client
14
14
  include Protocol::Methods
15
15
 
16
- def initialize(device: nil, glob: nil, logger: nil, retries: true, thread_safe: false)
16
+ def initialize(device: nil, glob: nil, logger: nil, retries: true,
17
+ timeout: nil, thread_safe: false
18
+ )
17
19
  @logger = logger
18
20
 
19
21
  @device = device
@@ -29,6 +31,7 @@ module Seriamp
29
31
  else
30
32
  raise ArgumentError, "retries must be an integer, true, false or nil: #{retries}"
31
33
  end
34
+ @timeout = timeout || DEFAULT_RS232_TIMEOUT
32
35
  @thread_safe = !!thread_safe
33
36
 
34
37
  if thread_safe?
@@ -48,6 +51,7 @@ module Seriamp
48
51
  attr_reader :glob
49
52
  attr_reader :logger
50
53
  attr_reader :retries
54
+ attr_reader :timeout
51
55
 
52
56
  def thread_safe?
53
57
  @thread_safe
@@ -94,7 +98,7 @@ module Seriamp
94
98
  def open_device
95
99
  if detect_device? && device.nil?
96
100
  logger&.debug("Detecting device")
97
- @device = Seriamp.detect_device(Integra, *glob, logger: logger)
101
+ @device = Seriamp.detect_device(Integra, *glob, logger: logger, timeout: timeout)
98
102
  if @device
99
103
  logger&.info("Using #{device} as TTY device")
100
104
  else
@@ -133,7 +137,7 @@ module Seriamp
133
137
 
134
138
  def read_response
135
139
  resp = +''
136
- deadline = Utils.monotime + 1
140
+ deadline = Utils.monotime + timeout
137
141
  loop do
138
142
  begin
139
143
  chunk = @io.read_nonblock(1000)
@@ -14,17 +14,22 @@ module Seriamp
14
14
  def initialize(args = ARGV, stdin = STDIN)
15
15
  options = {}
16
16
  OptionParser.new do |opts|
17
- opts.banner = "Usage: integra [-d device] command arg..."
17
+ opts.banner = "Usage: integra [options] command arg..."
18
18
 
19
19
  opts.on("-d", "--device DEVICE", "TTY to use (default autodetect)") do |v|
20
20
  options[:device] = v
21
21
  end
22
+
23
+ opts.on('-T', '--timeout TIMEOUT', 'Timeout to use') do |v|
24
+ options[:timeout] = Float(v)
25
+ end
22
26
  end.parse!
23
27
 
24
28
  @options = options
25
29
 
26
30
  @logger = Logger.new(STDERR)
27
- @client = Integra::Client.new(device: options[:device], logger: @logger)
31
+ @client = Integra::Client.new(device: options[:device],
32
+ logger: @logger, timeout: options[:timeout])
28
33
 
29
34
  @args = args
30
35
  @stdin = stdin
@@ -33,6 +38,7 @@ module Seriamp
33
38
  attr_reader :args
34
39
  attr_reader :stdin
35
40
  attr_reader :logger
41
+ attr_reader :options
36
42
 
37
43
  def run
38
44
  if args.any?
@@ -74,7 +80,7 @@ module Seriamp
74
80
  attr_reader :client
75
81
 
76
82
  def executor
77
- @executor ||= Executor.new(client)
83
+ @executor ||= Executor.new(client, timeout: options[:timeout])
78
84
  end
79
85
  end
80
86
  end
@@ -3,17 +3,19 @@
3
3
  module Seriamp
4
4
  module Integra
5
5
  class Executor
6
- def initialize(client)
6
+ def initialize(client, **opts)
7
7
  @client = client
8
+ @options = opts.dup.freeze
8
9
  end
9
10
 
10
11
  attr_reader :client
12
+ attr_reader :options
11
13
 
12
14
  def run_command(cmd, *args)
13
15
  cmd = cmd.gsub('_', '-')
14
16
  case cmd
15
17
  when 'detect'
16
- device = Seriamp.detect_device(Integra, *args, logger: logger)
18
+ device = Seriamp.detect_device(Integra, *args, logger: logger, timeout: options[:timeout])
17
19
  if device
18
20
  puts device
19
21
  exit 0
@@ -46,6 +46,7 @@ module Seriamp
46
46
  put '/zone/:zone/power' do |zone|
47
47
  state = Utils.parse_on_off(request.body.read)
48
48
  client.set_zone_power(Integer(zone), state)
49
+ empty_response
49
50
  end
50
51
 
51
52
  get '/zone/:zone/volume' do |zone|
@@ -98,6 +99,15 @@ module Seriamp
98
99
  headers['content-type'] = 'application/json'
99
100
  data.to_json
100
101
  end
102
+
103
+ def empty_response
104
+ [204, '']
105
+ end
106
+
107
+ error InvalidOnOffValue do |e|
108
+ headers['content-type'] = 'text/plain'
109
+ [422, "Error: #{e.class}: #{e}"]
110
+ end
101
111
  end
102
112
  end
103
113
  end
@@ -7,10 +7,12 @@ require 'seriamp/backend'
7
7
  module Seriamp
8
8
  module Sonamp
9
9
 
10
- RS232_TIMEOUT = 3
10
+ DEFAULT_RS232_TIMEOUT = 3
11
11
 
12
12
  class Client
13
- def initialize(device: nil, glob: nil, logger: nil, retries: true, thread_safe: false)
13
+ def initialize(device: nil, glob: nil, logger: nil, retries: true,
14
+ timeout: nil, thread_safe: false
15
+ )
14
16
  @logger = logger
15
17
 
16
18
  @device = device
@@ -26,6 +28,7 @@ module Seriamp
26
28
  else
27
29
  raise ArgumentError, "retries must be an integer, true, false or nil: #{retries}"
28
30
  end
31
+ @timeout = timeout || DEFAULT_RS232_TIMEOUT
29
32
  @thread_safe = !!thread_safe
30
33
 
31
34
  if thread_safe?
@@ -37,6 +40,7 @@ module Seriamp
37
40
  attr_reader :glob
38
41
  attr_reader :logger
39
42
  attr_reader :retries
43
+ attr_reader :timeout
40
44
 
41
45
  def thread_safe?
42
46
  @thread_safe
@@ -213,7 +217,7 @@ module Seriamp
213
217
  def open_device
214
218
  if detect_device? && device.nil?
215
219
  logger&.debug("Detecting device")
216
- @device = Seriamp.detect_device(Sonamp, *glob, logger: logger)
220
+ @device = Seriamp.detect_device(Sonamp, *glob, logger: logger, timeout: timeout)
217
221
  if @device
218
222
  logger&.info("Using #{device} as TTY device")
219
223
  else
@@ -305,13 +309,13 @@ module Seriamp
305
309
  end
306
310
 
307
311
  def with_timeout(&block)
308
- Timeout.timeout(RS232_TIMEOUT, CommunicationTimeout, &block)
312
+ Timeout.timeout(timeout, CommunicationTimeout, &block)
309
313
  end
310
314
 
311
315
  def read_line(f, cmd)
312
316
  with_timeout do
313
317
  resp = +''
314
- deadline = Utils.monotime + 1
318
+ deadline = Utils.monotime + timeout
315
319
  loop do
316
320
  begin
317
321
  buf = f.read_nonblock(1024)
@@ -351,8 +355,12 @@ module Seriamp
351
355
  if zone < 1 || zone > 4
352
356
  raise ArgumentError, "Zone must be between 1 and 4: #{zone}"
353
357
  end
354
- resp = dispatch(":#{cmd_prefix}#{zone}?")
355
- typecast_value(resp[cmd_prefix.length + 1..], boolize)
358
+ sent_prefix = "#{cmd_prefix}#{zone}"
359
+ resp = dispatch(":#{sent_prefix}?")
360
+ unless resp.start_with?(sent_prefix)
361
+ raise UnexpectedResponse, "Expected #{sent_prefix}..., received #{resp}"
362
+ end
363
+ typecast_value(resp[sent_prefix.length..], boolize)
356
364
  else
357
365
  range = include_all ? [1, 2, 3, 4, 'A'] : (1..4).to_a
358
366
  hashize_query_result(dispatch(":#{cmd_prefix}G?", range), cmd_prefix, boolize, range)
@@ -15,17 +15,22 @@ module Seriamp
15
15
 
16
16
  options = {}
17
17
  OptionParser.new do |opts|
18
- opts.banner = "Usage: sonamp [-d device] command arg..."
18
+ opts.banner = "Usage: sonamp [options] command arg..."
19
19
 
20
20
  opts.on("-d", "--device DEVICE", "TTY to use (default autodetect)") do |v|
21
21
  options[:device] = v
22
22
  end
23
+
24
+ opts.on('-T', '--timeout TIMEOUT', 'Timeout to use') do |v|
25
+ options[:timeout] = Float(v)
26
+ end
23
27
  end.parse!(args)
24
28
 
25
29
  @options = options
26
30
 
27
31
  @logger = Logger.new(STDERR)
28
- @client = Sonamp::Client.new(device: options[:device], logger: @logger)
32
+ @client = Sonamp::Client.new(device: options[:device],
33
+ logger: @logger, timeout: options[:timeout])
29
34
 
30
35
  @args = args
31
36
  @stdin = stdin
@@ -34,6 +39,7 @@ module Seriamp
34
39
  attr_reader :args
35
40
  attr_reader :stdin
36
41
  attr_reader :logger
42
+ attr_reader :options
37
43
 
38
44
  def run
39
45
  if args.any?
@@ -3,16 +3,18 @@
3
3
  module Seriamp
4
4
  module Sonamp
5
5
  class Executor
6
- def initialize(client)
6
+ def initialize(client, **opts)
7
7
  @client = client
8
+ @options = opts.dup.freeze
8
9
  end
9
10
 
10
11
  attr_reader :client
12
+ attr_reader :options
11
13
 
12
14
  def run_command(cmd, *args)
13
15
  case cmd
14
16
  when 'detect'
15
- device = Seriamp.detect_device(Sonamp, *args, logger: logger)
17
+ device = Seriamp.detect_device(Sonamp, *args, logger: logger, timeout: options[:timeout])
16
18
  if device
17
19
  puts device
18
20
  exit 0
data/lib/seriamp/utils.rb CHANGED
@@ -10,7 +10,7 @@ module Seriamp
10
10
  when '0', 'off', 'no', 'false'
11
11
  false
12
12
  else
13
- raise ArgumentError, "Invalid on/off value: #{value}"
13
+ raise InvalidOnOffValue, "Invalid on/off value: #{value}"
14
14
  end
15
15
  end
16
16
 
@@ -20,19 +20,20 @@ module Seriamp
20
20
 
21
21
  module_function def consume_data(io, logger, msg)
22
22
  warned = false
23
- read_bytes = 0
23
+ buf = +''
24
24
  while IO.select([io], nil, nil, 0)
25
25
  unless warned
26
26
  logger&.warn(msg)
27
27
  warned = true
28
28
  end
29
- buf = io.read_nonblock(1024)
30
- read_bytes += buf.length
29
+ buf += io.read_nonblock(1024)
31
30
  end
32
- if read_bytes > 0
33
- logger&.warn("Consumed #{read_bytes} bytes")
31
+ if buf.empty?
32
+ nil
33
+ else
34
+ logger&.warn("Consumed #{buf.length} bytes")
35
+ buf
34
36
  end
35
- nil
36
37
  end
37
38
  end
38
39
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Seriamp
4
- VERSION = '0.2.1'
4
+ VERSION = '0.2.2'
5
5
  end
@@ -9,12 +9,14 @@ module Seriamp
9
9
  module Yamaha
10
10
 
11
11
  # The manual says response should be received in 500 ms.
12
- RS232_TIMEOUT = 0.75
12
+ DEFAULT_RS232_TIMEOUT = 0.75
13
13
 
14
14
  class Client
15
15
  include Protocol::Methods
16
16
 
17
- def initialize(device: nil, glob: nil, logger: nil, retries: true, thread_safe: false)
17
+ def initialize(device: nil, glob: nil, logger: nil, retries: true,
18
+ timeout: nil, thread_safe: false
19
+ )
18
20
  @logger = logger
19
21
 
20
22
  @device = device
@@ -30,6 +32,7 @@ module Seriamp
30
32
  else
31
33
  raise ArgumentError, "retries must be an integer, true, false or nil: #{retries}"
32
34
  end
35
+ @timeout = timeout || DEFAULT_RS232_TIMEOUT
33
36
  @thread_safe = !!thread_safe
34
37
 
35
38
  if thread_safe?
@@ -49,6 +52,7 @@ module Seriamp
49
52
  attr_reader :glob
50
53
  attr_reader :logger
51
54
  attr_reader :retries
55
+ attr_reader :timeout
52
56
 
53
57
  def thread_safe?
54
58
  @thread_safe
@@ -186,7 +190,7 @@ module Seriamp
186
190
  def open_device
187
191
  if detect_device? && device.nil?
188
192
  logger&.debug("Detecting device")
189
- @device = Seriamp.detect_device(Yamaha, *glob, logger: logger)
193
+ @device = Seriamp.detect_device(Yamaha, *glob, logger: logger, timeout: timeout)
190
194
  if @device
191
195
  logger&.info("Using #{device} as TTY device")
192
196
  else
@@ -197,13 +201,14 @@ module Seriamp
197
201
  logger&.debug("Opening #{device}")
198
202
  @io = Backend::SerialPortBackend::Device.new(device, logger: logger)
199
203
 
200
- Utils.consume_data(@io.io, logger,
204
+ buf = Utils.consume_data(@io.io, logger,
201
205
  "Serial device readable after opening - unread previous response?")
206
+ report_unread_response(buf)
202
207
 
203
208
  begin
204
209
  tries = 0
205
210
  begin
206
- do_status
211
+ #do_status
207
212
  rescue CommunicationTimeout
208
213
  tries += 1
209
214
  if tries < 5
@@ -245,7 +250,7 @@ module Seriamp
245
250
 
246
251
  def read_response
247
252
  resp = +''
248
- deadline = Utils.monotime + 1
253
+ deadline = Utils.monotime + timeout
249
254
  loop do
250
255
  begin
251
256
  chunk = @io.read_nonblock(1000)
@@ -261,12 +266,18 @@ module Seriamp
261
266
  IO.select([@io.io], nil, nil, budget)
262
267
  end
263
268
  end
269
+
270
+ if resp.count(ETX) > 1
271
+ logger&.warn("Multiple responses received: #{resp}")
272
+ end
273
+
264
274
  resp
265
275
  end
266
276
 
267
277
  MODEL_NAMES = {
268
278
  'R0177' => 'RX-V1500',
269
279
  'R0178' => 'RX-V2500',
280
+ 'R0226' => 'RX-V1800',
270
281
  }.freeze
271
282
 
272
283
  PURE_DIRECT_FIELD = {
@@ -296,8 +307,9 @@ module Seriamp
296
307
  with_retry do
297
308
  resp = nil
298
309
  resp = dispatch(STATUS_REQ)
299
- Utils.consume_data(@io.io, logger,
310
+ buf = Utils.consume_data(@io.io, logger,
300
311
  "Serial device readable after completely reading status response - concurrent access?")
312
+ report_unread_response(buf)
301
313
  if resp.length < 10
302
314
  raise HandshakeFailure, "Broken status response: expected at least 10 bytes, got #{resp.length} bytes; concurrent operation on device?"
303
315
  end
@@ -431,6 +443,28 @@ module Seriamp
431
443
  end
432
444
  end
433
445
  end
446
+
447
+ def report_unread_response(buf)
448
+ return if buf.nil?
449
+
450
+ if buf.count(ETX) > 1
451
+ logger&.warn("Multiple unread responses: #{buf}")
452
+
453
+ buf.split(ETX).each do |resp|
454
+ report_unread_response(resp + ETX)
455
+ end
456
+ return
457
+ end
458
+
459
+ case buf[0]
460
+ when DC2
461
+ logger&.warn("Status response, #{buf.length} bytes")
462
+ when STX
463
+ logger&.warn("Command response: #{buf}")
464
+ else
465
+ logger&.warn("Unknown unread response: #{buf}")
466
+ end
467
+ end
434
468
  end
435
469
  end
436
470
  end
@@ -14,17 +14,22 @@ module Seriamp
14
14
  def initialize(args = ARGV, stdin = STDIN)
15
15
  options = {}
16
16
  OptionParser.new do |opts|
17
- opts.banner = "Usage: yamaha [-d device] command arg..."
17
+ opts.banner = "Usage: yamaha [options] command arg..."
18
18
 
19
19
  opts.on("-d", "--device DEVICE", "TTY to use (default autodetect)") do |v|
20
20
  options[:device] = v
21
21
  end
22
+
23
+ opts.on('-T', '--timeout TIMEOUT', 'Timeout to use') do |v|
24
+ options[:timeout] = Float(v)
25
+ end
22
26
  end.parse!
23
27
 
24
28
  @options = options
25
29
 
26
30
  @logger = Logger.new(STDERR)
27
- @client = Yamaha::Client.new(device: options[:device], logger: @logger)
31
+ @client = Yamaha::Client.new(device: options[:device],
32
+ logger: @logger, timeout: options[:timeout])
28
33
 
29
34
  @args = args
30
35
  @stdin = stdin
@@ -33,6 +38,7 @@ module Seriamp
33
38
  attr_reader :args
34
39
  attr_reader :stdin
35
40
  attr_reader :logger
41
+ attr_reader :options
36
42
 
37
43
  def run
38
44
  if args.any?
@@ -74,7 +80,7 @@ module Seriamp
74
80
  attr_reader :client
75
81
 
76
82
  def executor
77
- @executor ||= Executor.new(client)
83
+ @executor ||= Executor.new(client, timeout: options[:timeout])
78
84
  end
79
85
  end
80
86
  end
@@ -3,17 +3,19 @@
3
3
  module Seriamp
4
4
  module Yamaha
5
5
  class Executor
6
- def initialize(client)
6
+ def initialize(client, **opts)
7
7
  @client = client
8
+ @options = opts.dup.freeze
8
9
  end
9
10
 
10
11
  attr_reader :client
12
+ attr_reader :options
11
13
 
12
14
  def run_command(cmd, *args)
13
15
  cmd = cmd.gsub('_', '-')
14
16
  case cmd
15
17
  when 'detect'
16
- device = Seriamp.detect_device(Yamaha, *args, logger: logger)
18
+ device = Seriamp.detect_device(Yamaha, *args, logger: logger, timeout: options[:timeout])
17
19
  if device
18
20
  puts device
19
21
  exit 0
data/seriamp.gemspec CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Gem::Specification.new do |spec|
4
4
  spec.name = "seriamp"
5
- spec.version = '0.2.1'
5
+ spec.version = '0.2.2'
6
6
  spec.authors = ['Oleg Pudeyev']
7
7
  spec.email = ['code@olegp.name']
8
8
  spec.summary = %q{Serial control for amplifiers & A/V receivers}
@@ -39,4 +39,26 @@ describe Seriamp::Sonamp::App do
39
39
  JSON.parse(last_response.body).should == final_state
40
40
  end
41
41
  end
42
+
43
+ describe '/zone/:zone/power' do
44
+ it 'works' do
45
+ client.should_receive(:set_zone_power).with(2, true)
46
+
47
+ put '/zone/2/power', 'true'
48
+
49
+ last_response.status.should == 204
50
+ p last_response.body.should == ''
51
+ end
52
+
53
+ context 'when value is invalid' do
54
+ it 'returns 422' do
55
+ client.should_not receive(:set_zone_power)
56
+
57
+ put '/zone/2/power', 'bogus'
58
+
59
+ last_response.status.should == 422
60
+ last_response.body.should =~ /\AError: .* bogus/
61
+ end
62
+ end
63
+ end
42
64
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: seriamp
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Oleg Pudeyev
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-02-14 00:00:00.000000000 Z
11
+ date: 2023-02-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: serialport