seriamp 0.2.1 → 0.2.2

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.
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