seriamp 0.1.11 → 0.1.13

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: f9a46cb5fe16a4b24f54a6552e98db18e6e97281f7d12ce76c78f1ca5d18d8b8
4
- data.tar.gz: '082c51ad737e37e237248acca21d9ee6d0b74bca0be135cf99f52fbf26679c31'
3
+ metadata.gz: 608c49104ee16a8740439f09911464a51539d1a52907752bc7f0bf31fa0da85c
4
+ data.tar.gz: dc11ec89924b2c7b06a7a1a44be13e64c832de805c0c039f4a7bf3d66964a876
5
5
  SHA512:
6
- metadata.gz: 2b450a12d445a5b9199212eee5dba2eac298a9aa19e0e826df2fd322cd1d86d78a05815d39b3f75e417afa357720edb5ead5d1c9bfff1efefc171cb0b7e73f6e
7
- data.tar.gz: 9461488acf8478e6f7d52ea4f824042eede1a13a99f357092f37f55afddee6608e70a0306f23844a12a594e4658c5f87a40b1b2e090539a910f6ef963acd52ae
6
+ metadata.gz: 98879e190d39e69030155b492c72d3543e1db219f5315721705909b3cc177c1c150b6b5c3d5b0d3cb58ffccc59ea8d0610a064d95d5159ed11a91c7289a2ba27
7
+ data.tar.gz: 8f766224a06c19e14a71d5aa290d4c035031fd89a6abe1be1bd962a6fa36306d720e2032ac1ab804425cb40dcdbb6b84c050a15ebc77d8bec3b51673b8428cb5
data/README.md CHANGED
@@ -32,6 +32,39 @@ necessary.
32
32
  - Null-modem cable required
33
33
  - Receiver socket is male
34
34
 
35
+ The following table shows which Yamaha receivers have RS-232 connector
36
+ and which do not:
37
+
38
+ | Family | RS-232C Present | RS-232C Absent |
39
+ | -------- | ---------------------------- | -------------- |
40
+ | RX-Vx000 | RX-V3000, RX-V1000 | |
41
+ | | | HTR-5280 |
42
+ | RX-Vx200 | RX-V2200 | RX-V1200 |
43
+ | | | HTR-5490 |
44
+ | RX-Vx300 | RX-V2300 | RX-V1300 |
45
+ | | | HTR-5590 |
46
+ | | | HTR-5660 |
47
+ | RX-Vx400 | RX-V2400 | RX-V1400 |
48
+ | | | HTR-5790 |
49
+ | RX-Vx500 | RX-V2500, RX-V1500 | |
50
+ | | HTR-5890 | HTR-5860 |
51
+ | RX-Vx600 | RX-V2600, RX-V1600 | |
52
+ | | HTR-5990 | HTR-5960 |
53
+ | RX-Vx700 | RX-V2700, RX-V1700 | |
54
+ | | | HTR-6090 |
55
+ | RX-Vx800 | RX-V3800, RX-V1800 | |
56
+ | | HTR-6190 | HTR-6180 |
57
+ | RX-Vx900 | RX-V3900, RX-V1900 | |
58
+ | | | HTR-6290 |
59
+ | RX-Vx67 | RX-V3067, RX-V2067, RX-V1067 | RX-V867 |
60
+
61
+ RX-V2700, RX-V3800 and RX-V3900 have an Ethernet port in addition to
62
+ RS-232C and should be controllable via the Yamaha YNCA protocol via the
63
+ Ethernet port. Over time Yamaha has been adding networking functionality
64
+ to lower tier models, for example it is present in RX-V867, RX-V671 and RX-V475.
65
+
66
+ Models lower than 1000 level receivers have never had RS-232C to my knowledge.
67
+
35
68
  ### Denon AVR-2308CI
36
69
 
37
70
  - Straight cable required
@@ -111,6 +144,10 @@ Serial port communication in Ruby:
111
144
  - [rubyserial](https://github.com/hybridgroup/rubyserial)
112
145
  - [Ruby/SerialPort](https://github.com/hparra/ruby-serialport)
113
146
 
147
+ Yamaha YNCA protocol:
148
+
149
+ - [yamaha_ynca](https://github.com/mvdwetering/yamaha_ynca)
150
+
114
151
  ## Helpful Links
115
152
 
116
153
  - [Serial port programming in Ruby](https://www.thegeekdiary.com/serial-port-programming-reading-writing-status-of-control-lines-dtr-rts-cts-dsr/)
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(device: options[:device], logger: logger)
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(device: options[:device], logger: logger)
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))
@@ -21,7 +21,7 @@ module Seriamp
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).present?
24
+ client_cls.new(device: device, logger: logger, retries: false).present?
25
25
  logger&.debug("Found #{mod} device at #{device}")
26
26
  queue << device
27
27
  end
@@ -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, logger: settings.logger)
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,7 +10,7 @@ module Seriamp
10
10
  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, thread_safe: false)
14
14
  @logger = logger
15
15
 
16
16
  @device = device
@@ -26,6 +26,11 @@ module Seriamp
26
26
  else
27
27
  raise ArgumentError, "retries must be an integer, true, false or nil: #{retries}"
28
28
  end
29
+ @thread_safe = !!thread_safe
30
+
31
+ if thread_safe?
32
+ @lock = Mutex.new
33
+ end
29
34
  end
30
35
 
31
36
  attr_reader :device
@@ -33,6 +38,10 @@ module Seriamp
33
38
  attr_reader :logger
34
39
  attr_reader :retries
35
40
 
41
+ def thread_safe?
42
+ @thread_safe
43
+ end
44
+
36
45
  def detect_device?
37
46
  @detect_device
38
47
  end
@@ -182,10 +191,22 @@ module Seriamp
182
191
  private
183
192
 
184
193
  def with_device(&block)
185
- if @io
186
- yield @io
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
187
208
  else
188
- open_device(&block)
209
+ yield
189
210
  end
190
211
  end
191
212
 
@@ -203,6 +224,15 @@ module Seriamp
203
224
  logger&.debug("Opening #{device}")
204
225
  @io = Backend::SerialPortBackend::Device.new(device, logger: logger)
205
226
 
227
+ warned = false
228
+ while IO.select([@io.io], nil, nil, 0)
229
+ unless warned
230
+ logger&.warn("Serial device readable after opening - unread previous response?")
231
+ warned = true
232
+ end
233
+ IO.read(1)
234
+ end
235
+
206
236
  begin
207
237
  yield @io
208
238
  ensure
@@ -219,7 +249,9 @@ module Seriamp
219
249
  if try <= retries
220
250
  logger&.warn("Error during operation: #{exc.class}: #{exc} - will retry")
221
251
  try += 1
222
- @device = nil
252
+ if detect_device?
253
+ @device = nil
254
+ end
223
255
  retry
224
256
  else
225
257
  raise
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Seriamp
4
- VERSION = '0.1.11'
4
+ VERSION = '0.1.13'
5
5
  end
@@ -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, logger: settings.logger)
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
- with_device do
60
- unless @status
61
- do_status
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
- with_device do
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
- with_retry do
138
- with_device do
139
- @io.syswrite("#{STX}21000#{ETX}".encode('ascii'))
140
- @io.syswrite("#{STX}3#{msg[0..3]}#{ETX}".encode('ascii'))
141
- @io.syswrite("#{STX}3#{msg[4..7]}#{ETX}".encode('ascii'))
142
- @io.syswrite("#{STX}3#{msg[8..11]}#{ETX}".encode('ascii'))
143
- @io.syswrite("#{STX}3#{msg[12..15]}#{ETX}".encode('ascii'))
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
 
@@ -165,6 +197,15 @@ module Seriamp
165
197
  logger&.debug("Opening #{device}")
166
198
  @io = Backend::SerialPortBackend::Device.new(device, logger: logger)
167
199
 
200
+ warned = false
201
+ while IO.select([@io.io], nil, nil, 0)
202
+ unless warned
203
+ logger&.warn("Serial device readable after opening - unread previous response?")
204
+ warned = true
205
+ end
206
+ IO.read(1)
207
+ end
208
+
168
209
  begin
169
210
  tries = 0
170
211
  begin
@@ -265,9 +306,12 @@ module Seriamp
265
306
  end
266
307
  break unless again
267
308
  end
309
+ if resp.length < 10
310
+ raise HandshakeFailure, "Broken status response: expected at least 10 bytes, got #{resp.length} bytes; concurrent operation on device?"
311
+ end
268
312
  payload = resp[1...-1]
269
- @model_code = payload[0..4]
270
- @version = payload[5]
313
+ model_code = payload[0..4]
314
+ version = payload[5]
271
315
  length = payload[6..7].to_i(16)
272
316
  data = payload[8...-2]
273
317
  if data.length != length
@@ -276,11 +320,11 @@ module Seriamp
276
320
  unless data.start_with?('@E01900')
277
321
  raise HandshakeFailure, "Broken status response: expected to start with @E01900, actual #{data[0..6]}"
278
322
  end
279
- @status_string = data
280
- @status = {
281
- model_code: @model_code,
282
- model_name: MODEL_NAMES[@model_code],
283
- firmware_version: @version,
323
+ status_string = data
324
+ status = {
325
+ model_code: model_code,
326
+ model_name: MODEL_NAMES[model_code],
327
+ firmware_version: version,
284
328
  system_status: data[7].ord - ZERO_ORD,
285
329
  power: power = data[8].ord - ZERO_ORD,
286
330
  main_power: [1, 4, 5, 2].include?(power),
@@ -288,7 +332,7 @@ module Seriamp
288
332
  zone3_power: [1, 5, 3, 7].include?(power),
289
333
  }
290
334
  if data.length > 9
291
- @status.update(
335
+ status.update(
292
336
  input: input = data[9],
293
337
  input_name: MAIN_INPUTS_GET.fetch(input),
294
338
  multi_ch_input: data[10] == '1',
@@ -319,34 +363,41 @@ module Seriamp
319
363
  sleep: SLEEP_GET.fetch(data[24]),
320
364
  night: night = data[27],
321
365
  night_name: NIGHT_GET.fetch(night),
322
- pure_direct: data[PURE_DIRECT_FIELD.fetch(@model_code)] == '1',
366
+ pure_direct: data[PURE_DIRECT_FIELD.fetch(model_code)] == '1',
323
367
  speaker_a: data[29] == '1',
324
368
  speaker_b: data[30] == '1',
325
369
  # 2 positions on RX-Vx700
326
370
  #format: data[31..32],
327
371
  #sampling: data[33..34],
328
372
  )
329
- if @model_code == 'R0178'
330
- @status.update(
373
+ if model_code == 'R0178'
374
+ status.update(
331
375
  input_mode: INPUT_MODE_R0178.fetch(data[11]),
332
376
  sampling: data[32],
333
377
  sample_rate: SAMPLE_RATE_R0178.fetch(data[32]),
334
378
  )
335
379
  end
336
380
  end
337
- @status
381
+
382
+ @model_code, @version, @status_string =
383
+ model_code, version, status_string
384
+ @status = status
338
385
  end
339
386
  end
340
387
 
341
388
  def remote_command(cmd)
342
- with_retry do
343
- dispatch("#{STX}0#{cmd}#{ETX}")
389
+ with_lock do
390
+ with_retry do
391
+ dispatch("#{STX}0#{cmd}#{ETX}")
392
+ end
344
393
  end
345
394
  end
346
395
 
347
396
  def system_command(cmd)
348
- with_retry do
349
- dispatch("#{STX}2#{cmd}#{ETX}")
397
+ with_lock do
398
+ with_retry do
399
+ dispatch("#{STX}2#{cmd}#{ETX}")
400
+ end
350
401
  end
351
402
  end
352
403
 
@@ -379,7 +430,9 @@ module Seriamp
379
430
  if try <= retries
380
431
  logger&.warn("Error during operation: #{exc.class}: #{exc} - will retry")
381
432
  try += 1
382
- @device = nil
433
+ if detect_device?
434
+ @device = nil
435
+ end
383
436
  retry
384
437
  else
385
438
  raise
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.1.11'
5
+ spec.version = '0.1.13'
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}
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.11
4
+ version: 0.1.13
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-23 00:00:00.000000000 Z
11
+ date: 2023-01-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: serialport