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