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 +4 -4
- data/Gemfile.lock +1 -1
- data/README.md +67 -2
- data/lib/seriamp/detect.rb +3 -3
- data/lib/seriamp/error.rb +2 -0
- data/lib/seriamp/integra/client.rb +8 -4
- data/lib/seriamp/integra/cmd.rb +9 -3
- data/lib/seriamp/integra/executor.rb +4 -2
- data/lib/seriamp/sonamp/app.rb +10 -0
- data/lib/seriamp/sonamp/client.rb +15 -7
- data/lib/seriamp/sonamp/cmd.rb +8 -2
- data/lib/seriamp/sonamp/executor.rb +4 -2
- data/lib/seriamp/utils.rb +8 -7
- data/lib/seriamp/version.rb +1 -1
- data/lib/seriamp/yamaha/client.rb +41 -7
- data/lib/seriamp/yamaha/cmd.rb +9 -3
- data/lib/seriamp/yamaha/executor.rb +4 -2
- data/seriamp.gemspec +1 -1
- data/spec/sonamp/app_spec.rb +22 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bdacf1b53664adff670eaa918cdc44aa60ba20660249456b916a26c30f69ae08
|
4
|
+
data.tar.gz: 7f2cebc5518a380c4db21155735666481eef942d75f9ece04952ee1fac45c6e8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 301d7fb20466d009da6f9f9038da12b07e6d20491ada5d8b99ea03c96d09f1fc83c5c50d4d96ba900f98fd0b0da74a1c29da5f7afbe4d5c08c50832b06f05a8f
|
7
|
+
data.tar.gz: 6595c0244d77380aa768c41227d8f521ee4107a28d08fb55165500a0bc3abc6447db61afe0565472c14fc09a3795b0e8036f33b499a2b9a7cba794c857b15697
|
data/Gemfile.lock
CHANGED
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
|
data/lib/seriamp/detect.rb
CHANGED
@@ -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
|
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
@@ -8,12 +8,14 @@ require 'seriamp/integra/protocol/methods'
|
|
8
8
|
module Seriamp
|
9
9
|
module Integra
|
10
10
|
|
11
|
-
|
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,
|
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 +
|
140
|
+
deadline = Utils.monotime + timeout
|
137
141
|
loop do
|
138
142
|
begin
|
139
143
|
chunk = @io.read_nonblock(1000)
|
data/lib/seriamp/integra/cmd.rb
CHANGED
@@ -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 [
|
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],
|
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
|
data/lib/seriamp/sonamp/app.rb
CHANGED
@@ -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
|
-
|
10
|
+
DEFAULT_RS232_TIMEOUT = 3
|
11
11
|
|
12
12
|
class Client
|
13
|
-
def initialize(device: nil, glob: nil, logger: nil, retries: true,
|
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(
|
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 +
|
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
|
-
|
355
|
-
|
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)
|
data/lib/seriamp/sonamp/cmd.rb
CHANGED
@@ -15,17 +15,22 @@ module Seriamp
|
|
15
15
|
|
16
16
|
options = {}
|
17
17
|
OptionParser.new do |opts|
|
18
|
-
opts.banner = "Usage: sonamp [
|
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],
|
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
|
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
|
-
|
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
|
30
|
-
read_bytes += buf.length
|
29
|
+
buf += io.read_nonblock(1024)
|
31
30
|
end
|
32
|
-
if
|
33
|
-
|
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
|
data/lib/seriamp/version.rb
CHANGED
@@ -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
|
-
|
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,
|
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 +
|
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
|
data/lib/seriamp/yamaha/cmd.rb
CHANGED
@@ -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 [
|
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],
|
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
data/spec/sonamp/app_spec.rb
CHANGED
@@ -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.
|
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-
|
11
|
+
date: 2023-02-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: serialport
|