seriamp 0.1.10 → 0.1.12
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/bin/sonamp-web +4 -3
- data/bin/yamaha-web +4 -3
- data/lib/seriamp/sonamp/app.rb +3 -1
- data/lib/seriamp/sonamp/client.rb +100 -31
- data/lib/seriamp/version.rb +1 -1
- data/lib/seriamp/yamaha/app.rb +3 -1
- data/lib/seriamp/yamaha/client.rb +70 -28
- data/seriamp.gemspec +1 -1
- 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: 3a8c5974a39756c641ab5fb6e600d0d67095e4e9f18c872225ffb580cea4e70c
|
4
|
+
data.tar.gz: ba47852332bdb66070cf4105c4929ed691fce8eca8dd7f47aaefbeb4effab932
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 185e56b171c6db9ed0822f57de19ac2fea395d075bc615cddf0795c82c2f044b5212e9eb210b90b3dbc4aa603ce6b1553a610924c9ce8b9f02ae860c1f0c873c
|
7
|
+
data.tar.gz: 277dc1f126ca440e3a8539491e1548511f1abfb66e6a21c615ab4ab8bfce67c76c2499b6c6a2ec9efe20050cd1ac732f70cd3870cff98aec927ae57f6f46e1e4
|
data/bin/sonamp-web
CHANGED
@@ -23,9 +23,10 @@ end.parse!
|
|
23
23
|
|
24
24
|
logger = Logger.new(STDERR)
|
25
25
|
|
26
|
-
#Sonamp::App.set :device, options[:device]
|
27
|
-
#Sonamp::App.set :logger, logger
|
28
|
-
Seriamp::Sonamp::App.set :client, Seriamp::Sonamp::Client.new(
|
26
|
+
#Seriamp::Sonamp::App.set :device, options[:device]
|
27
|
+
#Seriamp::Sonamp::App.set :logger, logger
|
28
|
+
Seriamp::Sonamp::App.set :client, Seriamp::Sonamp::Client.new(
|
29
|
+
device: options[:device], logger: logger, thread_safe: true)
|
29
30
|
|
30
31
|
options = Rack::Server::Options.new.parse!(ARGV)
|
31
32
|
Rack::Server.start(options.merge(app: Seriamp::Sonamp::App))
|
data/bin/yamaha-web
CHANGED
@@ -23,9 +23,10 @@ end.parse!
|
|
23
23
|
|
24
24
|
logger = Logger.new(STDERR)
|
25
25
|
|
26
|
-
#Yamaha::App.set :device, options[:device]
|
27
|
-
#Yamaha::App.set :logger, logger
|
28
|
-
Seriamp::Yamaha::App.set :client, Seriamp::Yamaha::Client.new(
|
26
|
+
#Seriamp::Yamaha::App.set :device, options[:device]
|
27
|
+
#Seriamp::Yamaha::App.set :logger, logger
|
28
|
+
Seriamp::Yamaha::App.set :client, Seriamp::Yamaha::Client.new(
|
29
|
+
device: options[:device], logger: logger, thread_safe: true)
|
29
30
|
|
30
31
|
options = Rack::Server::Options.new.parse!(ARGV)
|
31
32
|
Rack::Server.start(options.merge(app: Seriamp::Yamaha::App))
|
data/lib/seriamp/sonamp/app.rb
CHANGED
@@ -12,6 +12,7 @@ module Seriamp
|
|
12
12
|
set :device, nil
|
13
13
|
set :logger, nil
|
14
14
|
set :client, nil
|
15
|
+
set :retries, true
|
15
16
|
|
16
17
|
get '/' do
|
17
18
|
render_json(client.status)
|
@@ -75,7 +76,8 @@ module Seriamp
|
|
75
76
|
|
76
77
|
def client
|
77
78
|
settings.client || begin
|
78
|
-
@client ||= Sonamp::Client.new(settings.device,
|
79
|
+
@client ||= Sonamp::Client.new(settings.device,
|
80
|
+
logger: settings.logger, retries: settings.retries, thread_safe: true)
|
79
81
|
end
|
80
82
|
end
|
81
83
|
|
@@ -10,17 +10,37 @@ module Seriamp
|
|
10
10
|
RS232_TIMEOUT = 3
|
11
11
|
|
12
12
|
class Client
|
13
|
-
def initialize(device: nil, glob: nil, logger: nil)
|
13
|
+
def initialize(device: nil, glob: nil, logger: nil, retries: true, thread_safe: false)
|
14
14
|
@logger = logger
|
15
15
|
|
16
16
|
@device = device
|
17
17
|
@detect_device = device.nil?
|
18
18
|
@glob = glob
|
19
|
+
@retries = case retries
|
20
|
+
when nil, false
|
21
|
+
0
|
22
|
+
when true
|
23
|
+
1
|
24
|
+
when Integer
|
25
|
+
retries
|
26
|
+
else
|
27
|
+
raise ArgumentError, "retries must be an integer, true, false or nil: #{retries}"
|
28
|
+
end
|
29
|
+
@thread_safe = !!thread_safe
|
30
|
+
|
31
|
+
if thread_safe?
|
32
|
+
@lock = Mutex.new
|
33
|
+
end
|
19
34
|
end
|
20
35
|
|
21
36
|
attr_reader :device
|
22
37
|
attr_reader :glob
|
23
38
|
attr_reader :logger
|
39
|
+
attr_reader :retries
|
40
|
+
|
41
|
+
def thread_safe?
|
42
|
+
@thread_safe
|
43
|
+
end
|
24
44
|
|
25
45
|
def detect_device?
|
26
46
|
@detect_device
|
@@ -134,7 +154,7 @@ module Seriamp
|
|
134
154
|
end
|
135
155
|
|
136
156
|
def get_voltage_trigger_input(zone = nil)
|
137
|
-
get_zone_state('VTI', zone)
|
157
|
+
get_zone_state('VTI', zone, include_all: true)
|
138
158
|
end
|
139
159
|
|
140
160
|
def get_firmware_version
|
@@ -148,7 +168,7 @@ module Seriamp
|
|
148
168
|
def status
|
149
169
|
# Reusing the opened device file makes :VTIG? fail even with a delay
|
150
170
|
# in front.
|
151
|
-
|
171
|
+
with_device do
|
152
172
|
{
|
153
173
|
firmware_version: get_firmware_version,
|
154
174
|
temperature: get_temperature,
|
@@ -165,13 +185,34 @@ module Seriamp
|
|
165
185
|
voltage_trigger_input: get_voltage_trigger_input,
|
166
186
|
channel_front_panel_level: get_channel_front_panel_level,
|
167
187
|
}
|
168
|
-
|
188
|
+
end
|
169
189
|
end
|
170
190
|
|
171
191
|
private
|
172
192
|
|
193
|
+
def with_device(&block)
|
194
|
+
with_lock do
|
195
|
+
if @io
|
196
|
+
yield @io
|
197
|
+
else
|
198
|
+
open_device(&block)
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
def with_lock
|
204
|
+
if thread_safe?
|
205
|
+
@lock.synchronize do
|
206
|
+
yield
|
207
|
+
end
|
208
|
+
else
|
209
|
+
yield
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
173
213
|
def open_device
|
174
214
|
if detect_device? && device.nil?
|
215
|
+
logger&.debug("Detecting device")
|
175
216
|
@device = Seriamp.detect_device(Sonamp, *glob, logger: logger)
|
176
217
|
if @device
|
177
218
|
logger&.info("Using #{device} as TTY device")
|
@@ -180,31 +221,58 @@ module Seriamp
|
|
180
221
|
end
|
181
222
|
end
|
182
223
|
|
183
|
-
|
224
|
+
logger&.debug("Opening #{device}")
|
225
|
+
@io = Backend::SerialPortBackend::Device.new(device, logger: logger)
|
226
|
+
|
227
|
+
begin
|
228
|
+
yield @io
|
229
|
+
ensure
|
230
|
+
@io.close rescue nil
|
231
|
+
@io = nil
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
def with_retry
|
236
|
+
try = 1
|
237
|
+
begin
|
184
238
|
yield
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
239
|
+
rescue Seriamp::Error => exc
|
240
|
+
if try <= retries
|
241
|
+
logger&.warn("Error during operation: #{exc.class}: #{exc} - will retry")
|
242
|
+
try += 1
|
243
|
+
@device = nil
|
244
|
+
retry
|
245
|
+
else
|
246
|
+
raise
|
191
247
|
end
|
192
|
-
rv
|
193
248
|
end
|
194
249
|
end
|
195
250
|
|
196
|
-
def dispatch(cmd,
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
251
|
+
def dispatch(cmd, resp_lines_range_or_count = 1)
|
252
|
+
resp_lines_range = if Range === resp_lines_range_or_count || Array === resp_lines_range_or_count
|
253
|
+
resp_lines_range_or_count
|
254
|
+
else
|
255
|
+
1..resp_lines_range_or_count
|
256
|
+
end
|
257
|
+
|
258
|
+
with_retry do
|
259
|
+
with_device do
|
260
|
+
with_timeout do
|
261
|
+
@io.syswrite("#{cmd}\x0d")
|
262
|
+
end
|
263
|
+
resp = resp_lines_range.map do
|
264
|
+
read_line(@io, cmd)
|
265
|
+
end
|
266
|
+
|
267
|
+
if @io && IO.select([@io.io], nil, nil, 0)
|
268
|
+
logger&.warn("Serial device readable after completely reading status response - concurrent access?")
|
269
|
+
end
|
270
|
+
|
271
|
+
if resp_lines_range_or_count == 1
|
272
|
+
resp.first
|
273
|
+
else
|
274
|
+
resp
|
275
|
+
end
|
208
276
|
end
|
209
277
|
end
|
210
278
|
end
|
@@ -266,7 +334,7 @@ module Seriamp
|
|
266
334
|
dispatch_assert(cmd, expected)
|
267
335
|
end
|
268
336
|
|
269
|
-
def get_zone_value(cmd_prefix, zone, boolize: false)
|
337
|
+
def get_zone_value(cmd_prefix, zone, boolize: false, include_all: false)
|
270
338
|
if zone
|
271
339
|
if zone < 1 || zone > 4
|
272
340
|
raise ArgumentError, "Zone must be between 1 and 4: #{zone}"
|
@@ -274,22 +342,23 @@ module Seriamp
|
|
274
342
|
resp = dispatch(":#{cmd_prefix}#{zone}?")
|
275
343
|
typecast_value(resp[cmd_prefix.length + 1..], boolize)
|
276
344
|
else
|
277
|
-
|
345
|
+
range = include_all ? [1, 2, 3, 4, 'A'] : (1..4).to_a
|
346
|
+
hashize_query_result(dispatch(":#{cmd_prefix}G?", range), cmd_prefix, boolize, range)
|
278
347
|
end
|
279
348
|
end
|
280
349
|
|
281
|
-
def hashize_query_result(resp_lines, cmd_prefix, boolize)
|
350
|
+
def hashize_query_result(resp_lines, cmd_prefix, boolize, range)
|
282
351
|
index = 1
|
283
352
|
Hash[resp_lines.map do |resp|
|
284
|
-
value = typecast_value(extract_suffix(resp, "#{cmd_prefix}#{index}"), boolize)
|
353
|
+
value = typecast_value(extract_suffix(resp, "#{cmd_prefix}#{range.to_a[index-1]}"), boolize)
|
285
354
|
[index, value].tap do
|
286
355
|
index += 1
|
287
356
|
end
|
288
357
|
end]
|
289
358
|
end
|
290
359
|
|
291
|
-
def get_zone_state(cmd_prefix, zone)
|
292
|
-
get_zone_value(cmd_prefix, zone, boolize: true)
|
360
|
+
def get_zone_state(cmd_prefix, zone, include_all: false)
|
361
|
+
get_zone_value(cmd_prefix, zone, boolize: true, include_all: include_all)
|
293
362
|
end
|
294
363
|
|
295
364
|
def set_channel_value(cmd_prefix, channel, value)
|
@@ -309,7 +378,7 @@ module Seriamp
|
|
309
378
|
typecast_value(dispatch_extract_suffix(":#{cmd_prefix}#{channel}?", "#{cmd_prefix}#{channel}"), boolize)
|
310
379
|
else
|
311
380
|
index = 1
|
312
|
-
hashize_query_result(dispatch(":#{cmd_prefix}G?", 8), cmd_prefix, boolize)
|
381
|
+
hashize_query_result(dispatch(":#{cmd_prefix}G?", 8), cmd_prefix, boolize, 1..8)
|
313
382
|
end
|
314
383
|
end
|
315
384
|
|
data/lib/seriamp/version.rb
CHANGED
data/lib/seriamp/yamaha/app.rb
CHANGED
@@ -13,6 +13,7 @@ module Seriamp
|
|
13
13
|
set :device, nil
|
14
14
|
set :logger, nil
|
15
15
|
set :client, nil
|
16
|
+
set :retries, true
|
16
17
|
|
17
18
|
get '/' do
|
18
19
|
clear_cache
|
@@ -153,7 +154,8 @@ module Seriamp
|
|
153
154
|
|
154
155
|
def client
|
155
156
|
settings.client || begin
|
156
|
-
@client ||= Yamaha::Client.new(settings.device,
|
157
|
+
@client ||= Yamaha::Client.new(settings.device,
|
158
|
+
logger: settings.logger, retries: settings.retries, thread_safe: true)
|
157
159
|
end
|
158
160
|
end
|
159
161
|
|
@@ -14,7 +14,7 @@ module Seriamp
|
|
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, thread_safe: false)
|
18
18
|
@logger = logger
|
19
19
|
|
20
20
|
@device = device
|
@@ -30,6 +30,11 @@ module Seriamp
|
|
30
30
|
else
|
31
31
|
raise ArgumentError, "retries must be an integer, true, false or nil: #{retries}"
|
32
32
|
end
|
33
|
+
@thread_safe = !!thread_safe
|
34
|
+
|
35
|
+
if thread_safe?
|
36
|
+
@lock = Mutex.new
|
37
|
+
end
|
33
38
|
|
34
39
|
if block_given?
|
35
40
|
begin
|
@@ -45,6 +50,10 @@ module Seriamp
|
|
45
50
|
attr_reader :logger
|
46
51
|
attr_reader :retries
|
47
52
|
|
53
|
+
def thread_safe?
|
54
|
+
@thread_safe
|
55
|
+
end
|
56
|
+
|
48
57
|
def detect_device?
|
49
58
|
@detect_device
|
50
59
|
end
|
@@ -56,18 +65,29 @@ module Seriamp
|
|
56
65
|
|
57
66
|
def last_status
|
58
67
|
unless @status
|
59
|
-
|
60
|
-
|
61
|
-
|
68
|
+
with_lock do
|
69
|
+
with_retry do
|
70
|
+
with_device do
|
71
|
+
unless @status
|
72
|
+
do_status
|
73
|
+
end
|
74
|
+
end
|
62
75
|
end
|
63
76
|
end
|
64
77
|
end
|
78
|
+
if @status.nil?
|
79
|
+
raise "This should not happen"
|
80
|
+
end
|
65
81
|
@status.dup
|
66
82
|
end
|
67
83
|
|
68
84
|
def last_status_string
|
69
85
|
unless @status_string
|
70
|
-
|
86
|
+
with_lock do
|
87
|
+
with_retry do
|
88
|
+
with_device do
|
89
|
+
end
|
90
|
+
end
|
71
91
|
end
|
72
92
|
end
|
73
93
|
@status_string.dup
|
@@ -121,6 +141,16 @@ module Seriamp
|
|
121
141
|
end
|
122
142
|
end
|
123
143
|
|
144
|
+
def with_lock
|
145
|
+
if thread_safe?
|
146
|
+
@lock.synchronize do
|
147
|
+
yield
|
148
|
+
end
|
149
|
+
else
|
150
|
+
yield
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
124
154
|
# Shows a message via the on-screen display. The message must be 16
|
125
155
|
# characters or fewer. The message is NOT displayed on the front panel,
|
126
156
|
# it is shown only on the connected TV's OSD.
|
@@ -134,13 +164,15 @@ module Seriamp
|
|
134
164
|
raise ArgumentError, "Message must be no more than 16 characters, #{msg.length} given"
|
135
165
|
end
|
136
166
|
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
167
|
+
with_lock do
|
168
|
+
with_retry do
|
169
|
+
with_device do
|
170
|
+
@io.syswrite("#{STX}21000#{ETX}".encode('ascii'))
|
171
|
+
@io.syswrite("#{STX}3#{msg[0..3]}#{ETX}".encode('ascii'))
|
172
|
+
@io.syswrite("#{STX}3#{msg[4..7]}#{ETX}".encode('ascii'))
|
173
|
+
@io.syswrite("#{STX}3#{msg[8..11]}#{ETX}".encode('ascii'))
|
174
|
+
@io.syswrite("#{STX}3#{msg[12..15]}#{ETX}".encode('ascii'))
|
175
|
+
end
|
144
176
|
end
|
145
177
|
end
|
146
178
|
|
@@ -265,9 +297,12 @@ module Seriamp
|
|
265
297
|
end
|
266
298
|
break unless again
|
267
299
|
end
|
300
|
+
if resp.length < 10
|
301
|
+
raise HandshakeFailure, "Broken status response: expected at least 10 bytes, got #{resp.length} bytes; concurrent operation on device?"
|
302
|
+
end
|
268
303
|
payload = resp[1...-1]
|
269
|
-
|
270
|
-
|
304
|
+
model_code = payload[0..4]
|
305
|
+
version = payload[5]
|
271
306
|
length = payload[6..7].to_i(16)
|
272
307
|
data = payload[8...-2]
|
273
308
|
if data.length != length
|
@@ -276,11 +311,11 @@ module Seriamp
|
|
276
311
|
unless data.start_with?('@E01900')
|
277
312
|
raise HandshakeFailure, "Broken status response: expected to start with @E01900, actual #{data[0..6]}"
|
278
313
|
end
|
279
|
-
|
280
|
-
|
281
|
-
model_code:
|
282
|
-
model_name: MODEL_NAMES[
|
283
|
-
firmware_version:
|
314
|
+
status_string = data
|
315
|
+
status = {
|
316
|
+
model_code: model_code,
|
317
|
+
model_name: MODEL_NAMES[model_code],
|
318
|
+
firmware_version: version,
|
284
319
|
system_status: data[7].ord - ZERO_ORD,
|
285
320
|
power: power = data[8].ord - ZERO_ORD,
|
286
321
|
main_power: [1, 4, 5, 2].include?(power),
|
@@ -288,7 +323,7 @@ module Seriamp
|
|
288
323
|
zone3_power: [1, 5, 3, 7].include?(power),
|
289
324
|
}
|
290
325
|
if data.length > 9
|
291
|
-
|
326
|
+
status.update(
|
292
327
|
input: input = data[9],
|
293
328
|
input_name: MAIN_INPUTS_GET.fetch(input),
|
294
329
|
multi_ch_input: data[10] == '1',
|
@@ -319,34 +354,41 @@ module Seriamp
|
|
319
354
|
sleep: SLEEP_GET.fetch(data[24]),
|
320
355
|
night: night = data[27],
|
321
356
|
night_name: NIGHT_GET.fetch(night),
|
322
|
-
pure_direct: data[PURE_DIRECT_FIELD.fetch(
|
357
|
+
pure_direct: data[PURE_DIRECT_FIELD.fetch(model_code)] == '1',
|
323
358
|
speaker_a: data[29] == '1',
|
324
359
|
speaker_b: data[30] == '1',
|
325
360
|
# 2 positions on RX-Vx700
|
326
361
|
#format: data[31..32],
|
327
362
|
#sampling: data[33..34],
|
328
363
|
)
|
329
|
-
if
|
330
|
-
|
364
|
+
if model_code == 'R0178'
|
365
|
+
status.update(
|
331
366
|
input_mode: INPUT_MODE_R0178.fetch(data[11]),
|
332
367
|
sampling: data[32],
|
333
368
|
sample_rate: SAMPLE_RATE_R0178.fetch(data[32]),
|
334
369
|
)
|
335
370
|
end
|
336
371
|
end
|
337
|
-
|
372
|
+
|
373
|
+
@model_code, @version, @status_string =
|
374
|
+
model_code, version, status_string
|
375
|
+
@status = status
|
338
376
|
end
|
339
377
|
end
|
340
378
|
|
341
379
|
def remote_command(cmd)
|
342
|
-
|
343
|
-
|
380
|
+
with_lock do
|
381
|
+
with_retry do
|
382
|
+
dispatch("#{STX}0#{cmd}#{ETX}")
|
383
|
+
end
|
344
384
|
end
|
345
385
|
end
|
346
386
|
|
347
387
|
def system_command(cmd)
|
348
|
-
|
349
|
-
|
388
|
+
with_lock do
|
389
|
+
with_retry do
|
390
|
+
dispatch("#{STX}2#{cmd}#{ETX}")
|
391
|
+
end
|
350
392
|
end
|
351
393
|
end
|
352
394
|
|
data/seriamp.gemspec
CHANGED
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.1.
|
4
|
+
version: 0.1.12
|
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-01-
|
11
|
+
date: 2023-01-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: serialport
|