seriamp 0.1.14 → 0.2.0

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: 5687443fb1ef40b23fe59092d92d5421ff5dae7fd5196ddcd682df99d8dd64bd
4
- data.tar.gz: 1c0654c533b4939cf5c265bfbab0f4e9ce21ea5a5b5506d1ef224d6f6ca2623d
3
+ metadata.gz: 9b1f037dbe23a5b8e4137f844d9c72a66e558ea68b17030b8f74def52cd52a2b
4
+ data.tar.gz: b0f1a36b133b59ac31f780da05104006ee17cdfa499db61cfec44aefe1a199f3
5
5
  SHA512:
6
- metadata.gz: 7385b186ea20acc6f6ba001de8e0498c20fddc41f12219ca8e81a5490dcdd2c3751b340fe7e0ad2c990bc1c48447a724a6d5d1831746708aa57aadf78a4a3f30
7
- data.tar.gz: b0411b2b26c3b8caa035d6286559e2daa98ec86d8d9d328e2ea324971458f1f6754fedf809d6db6f9d50336c9bba1953b2ffa5b36c6fdddc3d0bce85f8bb3e87
6
+ metadata.gz: 0d53f1ad5da19fb3750168a1e7da77e9e1c0914047e1a63b78ef8c67fbca7530ec40aaa619c5b1d226fca2c1f100519787b83a74a9690053e8cb6c742ecd9abc
7
+ data.tar.gz: 0a2229dce99ef4f84d2239d9f147740ec24319caf1b71560d2f1caa9cf203eb525d4551bd02041c4b1105d621ee109ffd78763856f36824d7596d3319699d94e
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ gem 'faraday', '~> 2.7'
6
+ gem 'sinatra', '~> 3.0'
7
+ gem 'rack-test'
8
+ gem 'byebug'
data/Gemfile.lock ADDED
@@ -0,0 +1,55 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ seriamp (0.1.14)
5
+ serialport (~> 1.3)
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ byebug (11.1.3)
11
+ diff-lcs (1.5.0)
12
+ faraday (2.7.4)
13
+ faraday-net_http (>= 2.0, < 3.1)
14
+ ruby2_keywords (>= 0.0.4)
15
+ faraday-net_http (3.0.2)
16
+ mustermann (3.0.0)
17
+ ruby2_keywords (~> 0.0.1)
18
+ rack (2.2.4)
19
+ rack-protection (3.0.5)
20
+ rack
21
+ rack-test (2.0.2)
22
+ rack (>= 1.3)
23
+ rspec-core (3.12.1)
24
+ rspec-support (~> 3.12.0)
25
+ rspec-expectations (3.12.2)
26
+ diff-lcs (>= 1.2.0, < 2.0)
27
+ rspec-support (~> 3.12.0)
28
+ rspec-mocks (3.12.3)
29
+ diff-lcs (>= 1.2.0, < 2.0)
30
+ rspec-support (~> 3.12.0)
31
+ rspec-support (3.12.0)
32
+ ruby2_keywords (0.0.5)
33
+ serialport (1.3.2)
34
+ sinatra (3.0.5)
35
+ mustermann (~> 3.0)
36
+ rack (~> 2.2, >= 2.2.4)
37
+ rack-protection (= 3.0.5)
38
+ tilt (~> 2.0)
39
+ tilt (2.0.11)
40
+
41
+ PLATFORMS
42
+ x86_64-linux
43
+
44
+ DEPENDENCIES
45
+ byebug
46
+ faraday (~> 2.7)
47
+ rack-test
48
+ rspec-core (~> 3.12)
49
+ rspec-expectations (~> 3.12)
50
+ rspec-mocks (~> 3.12)
51
+ seriamp!
52
+ sinatra (~> 3.0)
53
+
54
+ BUNDLED WITH
55
+ 2.3.15
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # Yamaha Receiver Serial Control Ruby Library
1
+ # Receiver & Amplifier Serial Control Ruby Library
2
2
 
3
3
  ## Hardware Requirements
4
4
 
@@ -70,9 +70,21 @@ Models lower than 1000 level receivers have never had RS-232C to my knowledge.
70
70
  - Straight cable required
71
71
  - Receiver socket is female
72
72
 
73
- ### Integra DTR
73
+ ### Integra
74
74
 
75
- - Straight cable required
75
+ Tested with DTR-50.4.
76
+
77
+ - Straight cable required (if using a USB to serial adapter which already
78
+ contains a serial cable with male end, no additional cable may be needed).
79
+ - Receiver socket is female.
80
+ - Receiver socket has the nuts for fixing the cable connector.
81
+ [This FTDI adapter](https://www.amazon.com/gp/product/B0759HSLP1)
82
+ has screws on the serial end and attaches directly to the receiver.
83
+ [This PL2303 adapter]((https://www.amazon.com/gp/product/B00IDSM6BW)
84
+ has nuts on the serial end and does not attach to the receiver, requiring
85
+ either a straight through female to male serial cable or removing the nuts
86
+ from one of the ends (the USB to serial adapter is the cheaper device,
87
+ I modify the adapters rather than the receivers/amplifiers).
76
88
 
77
89
  ## Protocol Notes
78
90
 
@@ -131,7 +143,7 @@ to open a TTY in Python, buffering must be disabled.
131
143
  See [here](https://www.avsforum.com/threads/enhancing-yamaha-avrs-via-rs-232.1066484/)
132
144
  for more Yamaha-related software.
133
145
 
134
- ## Other Libraries
146
+ ## Other Libraries & Tools
135
147
 
136
148
  Yamaha RS232/serial protocol:
137
149
 
@@ -148,6 +160,20 @@ Yamaha YNCA protocol:
148
160
 
149
161
  - [yamaha_ynca](https://github.com/mvdwetering/yamaha_ynca)
150
162
 
163
+ Integra serial control:
164
+
165
+ - [Many resources](https://www.avforums.com/threads/onkyo-tx-nr-1007-webinterface-programming.1107346/page-14)
166
+ - [onkyoweb-php](https://github.com/guikubivan/onkyoweb-php)
167
+ - [onkyo-eiscp](https://github.com/miracle2k/onkyo-eiscp)
168
+ - [onpc](https://github.com/mkulesh/onpc)
169
+ - [ISCP/eISCP](https://habr.com/en/post/427985/)
170
+ - [Decrypting Onkyo firmware](http://divideoverflow.com/2014/04/decrypting-onkyo-firmware-files/)
171
+ - [Post](https://robotskirts.com/2012/04/28/controlling-onkyo-integra-receivers-via-rs-232/)
172
+
173
+ Pioneer serial control:
174
+
175
+ - [Manuals](https://www.pioneerelectronics.com/PUSA/Support/Home-Entertainment-Custom-Install/RS-232+&+IP+Codes/A+V+Receivers)
176
+
151
177
  ## Helpful Links
152
178
 
153
179
  - [Serial port programming in Ruby](https://www.thegeekdiary.com/serial-port-programming-reading-writing-status-of-control-lines-dtr-rts-cts-dsr/)
data/bin/integra ADDED
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # frozen_string_literal: true
4
+
5
+ begin
6
+ require 'seriamp/integra/cmd'
7
+ rescue LoadError
8
+ $: << File.join(File.dirname(__FILE__), '../lib')
9
+ require 'seriamp/integra/cmd'
10
+ end
11
+
12
+ Seriamp::Integra::Cmd.new.run
data/bin/sonamp CHANGED
@@ -7,4 +7,4 @@ rescue LoadError
7
7
  require 'seriamp/sonamp/cmd'
8
8
  end
9
9
 
10
- Seriamp::Sonamp::Cmd.new(ARGV).run
10
+ Seriamp::Sonamp::Cmd.new.run
@@ -0,0 +1,56 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ begin
4
+ require 'seriamp/sonamp/auto_power'
5
+ rescue LoadError
6
+ $: << File.join(File.dirname(__FILE__), '../lib')
7
+ require 'seriamp/sonamp/auto_power'
8
+ end
9
+ require 'optparse'
10
+ require 'logger'
11
+
12
+ options = {}
13
+ OptionParser.new do |opts|
14
+ opts.banner = "Usage: sonamp-auto-power [-l log-path] [-D]"
15
+
16
+ opts.on("--daemonize", "-D", "Daemonize") do
17
+ options[:daemonize] = true
18
+ end
19
+
20
+ opts.on("--log=PATH", "-l", "Path to log file") do |path|
21
+ options[:log_path] = path
22
+ end
23
+
24
+ opts.on("--sonamp-url=URL", "-s", "Path to Sonamp webapp URL") do |v|
25
+ options[:sonamp_url] = v
26
+ end
27
+
28
+ opts.on("--state=PATH", "-f", "Path to state file") do |path|
29
+ options[:state_path] = path
30
+ end
31
+
32
+ opts.on("--ttl=TTL", "-t", "Delay in seconds for turning amplifier off after receiver is detected off") do |v|
33
+ options[:ttl] = Integer(v)
34
+ end
35
+
36
+ opts.on("--yamaha-url=URL", "-y", "Path to Yamaha webapp URL") do |v|
37
+ options[:yamaha_url] = v
38
+ end
39
+
40
+ end.parse!
41
+
42
+ logger = Logger.new(STDERR)
43
+
44
+ runner = Seriamp::Sonamp::AutoPower.new(
45
+ logger: logger,
46
+ sonamp_url: options[:sonamp_url],
47
+ state_path: options[:state_path],
48
+ ttl: options[:ttl],
49
+ yamaha_url: options[:yamaha_url],
50
+ )
51
+
52
+ if options[:daemonize]
53
+ Process.daemonize
54
+ end
55
+
56
+ runner.run
data/bin/yamaha CHANGED
@@ -9,4 +9,4 @@ rescue LoadError
9
9
  require 'seriamp/yamaha/cmd'
10
10
  end
11
11
 
12
- Seriamp::Yamaha::Cmd.new(ARGV).run
12
+ Seriamp::Yamaha::Cmd.new.run
@@ -0,0 +1,4 @@
1
+ require 'seriamp/yamaha/app'
2
+ require 'seriamp/yamaha/cmd'
3
+ require 'seriamp/sonamp/app'
4
+ require 'seriamp/sonamp/cmd'
@@ -27,7 +27,7 @@ module Seriamp
27
27
 
28
28
  attr_reader :io
29
29
 
30
- def_delegators :io, :close, :sysread, :syswrite
30
+ def_delegators :io, :close, :sysread, :syswrite, :read_nonblock
31
31
  end
32
32
  end
33
33
  end
@@ -0,0 +1,6 @@
1
+ module Seriamp
2
+ module Backend
3
+ autoload :FFIBackend, 'seriamp/backend/ffi'
4
+ autoload :SerialPortBackend, 'seriamp/backend/serial_port'
5
+ end
6
+ end
@@ -0,0 +1,89 @@
1
+ require 'faraday'
2
+ autoload :JSON, 'json'
3
+
4
+ module Seriamp
5
+ class FaradayFacade
6
+ class HttpError < StandardError
7
+ end
8
+
9
+ def initialize(**opts)
10
+ @options = {}
11
+ if timeout = opts.delete(:timeout)
12
+ @options[:timeout] = timeout
13
+ end
14
+
15
+ @conn = Faraday.new(**opts) do |faraday|
16
+ #faraday.response :logger, nil, { headers: true, bodies: false }
17
+ #faraday.response :logger
18
+ end
19
+
20
+ @base_url = opts.fetch(:url)
21
+ end
22
+
23
+ attr_reader :options
24
+ attr_reader :base_url
25
+
26
+ def get(uri)
27
+ conn.get(uri) do |req|
28
+ configure_request(req)
29
+ end
30
+ end
31
+
32
+ def get!(url)
33
+ resp = get(url)
34
+ if resp.status != 200
35
+ raise HttpError, "Bad status: #{resp.status} for #{base_url} #{url} with #{options}"
36
+ end
37
+ resp.body
38
+ end
39
+
40
+ def get_json(url)
41
+ resp = get!(url)
42
+ JSON.parse(resp)
43
+ end
44
+
45
+ def put(uri, body: nil)
46
+ conn.put(uri) do |req|
47
+ configure_request(req)
48
+ if body
49
+ req.body = body
50
+ end
51
+ end
52
+ end
53
+
54
+ def put!(url, **opts)
55
+ put(url, **opts).tap do |resp|
56
+ unless resp.success?
57
+ raise HttpError, "Bad status: #{resp.status} for #{url}"
58
+ end
59
+ end
60
+ end
61
+
62
+ def post(uri, body: nil)
63
+ conn.post(uri) do |req|
64
+ configure_request(req)
65
+ if body
66
+ req.body = body
67
+ end
68
+ end
69
+ end
70
+
71
+ def post!(url, **opts)
72
+ post(url, **opts).tap do |resp|
73
+ unless resp.success?
74
+ raise HttpError, "Bad status: #{resp.status} for #{url}"
75
+ end
76
+ end
77
+ end
78
+
79
+ private
80
+
81
+ attr_reader :conn
82
+
83
+ def configure_request(req)
84
+ req.options.timeout = options[:timeout]
85
+ req.options.read_timeout = options[:timeout]
86
+ req.options.open_timeout = options[:timeout]
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,192 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'timeout'
4
+ require 'seriamp/utils'
5
+ require 'seriamp/backend'
6
+ require 'seriamp/integra/protocol/methods'
7
+
8
+ module Seriamp
9
+ module Integra
10
+
11
+ RS232_TIMEOUT = 0.25
12
+
13
+ class Client
14
+ include Protocol::Methods
15
+
16
+ def initialize(device: nil, glob: nil, logger: nil, retries: true, thread_safe: false)
17
+ @logger = logger
18
+
19
+ @device = device
20
+ @detect_device = device.nil?
21
+ @glob = glob
22
+ @retries = case retries
23
+ when nil, false
24
+ 0
25
+ when true
26
+ 1
27
+ when Integer
28
+ retries
29
+ else
30
+ raise ArgumentError, "retries must be an integer, true, false or nil: #{retries}"
31
+ end
32
+ @thread_safe = !!thread_safe
33
+
34
+ if thread_safe?
35
+ @lock = Mutex.new
36
+ end
37
+
38
+ if block_given?
39
+ begin
40
+ yield self
41
+ ensure
42
+ close
43
+ end
44
+ end
45
+ end
46
+
47
+ attr_reader :device
48
+ attr_reader :glob
49
+ attr_reader :logger
50
+ attr_reader :retries
51
+
52
+ def thread_safe?
53
+ @thread_safe
54
+ end
55
+
56
+ def detect_device?
57
+ @detect_device
58
+ end
59
+
60
+ def get_power
61
+ boolean_question('PWR')
62
+ end
63
+
64
+ def status
65
+ {
66
+ power: get_power,
67
+ }
68
+ end
69
+
70
+ def with_device(&block)
71
+ if @io
72
+ yield @io
73
+ else
74
+ open_device(&block)
75
+ end
76
+ end
77
+
78
+ def with_lock
79
+ if thread_safe?
80
+ @lock.synchronize do
81
+ yield
82
+ end
83
+ else
84
+ yield
85
+ end
86
+ end
87
+
88
+ private
89
+
90
+ include Protocol::Constants
91
+
92
+ EOT = ?\x1a
93
+
94
+ def open_device
95
+ if detect_device? && device.nil?
96
+ logger&.debug("Detecting device")
97
+ @device = Seriamp.detect_device(Integra, *glob, logger: logger)
98
+ if @device
99
+ logger&.info("Using #{device} as TTY device")
100
+ else
101
+ raise NoDevice, "No device specified and device could not be detected automatically"
102
+ end
103
+ end
104
+
105
+ logger&.debug("Opening #{device}")
106
+ @io = Backend::SerialPortBackend::Device.new(device, logger: logger)
107
+
108
+ Utils.consume_data(@io.io, logger,
109
+ "Serial device readable after opening - unread previous response?")
110
+
111
+ begin
112
+ yield @io
113
+ ensure
114
+ @io.close rescue nil
115
+ @io = nil
116
+ end
117
+ end
118
+
119
+ def dispatch(cmd)
120
+ start = Utils.monotime
121
+ with_device do
122
+ @io.syswrite(cmd.encode('ascii'))
123
+ resp = read_response
124
+ unless resp =~ /\A!1.+\x1a\z/
125
+ raise "Malformed response: #{resp}"
126
+ end
127
+ resp[2...-1]
128
+ end.tap do
129
+ elapsed = Utils.monotime - start
130
+ logger&.debug("Integra: dispatched #{cmd} in #{'%.2f' % elapsed} s")
131
+ end
132
+ end
133
+
134
+ def read_response
135
+ resp = +''
136
+ deadline = Utils.monotime + 1
137
+ loop do
138
+ begin
139
+ chunk = @io.read_nonblock(1000)
140
+ if chunk
141
+ resp += chunk
142
+ break if chunk[-1] == EOT
143
+ end
144
+ rescue IO::WaitReadable
145
+ budget = deadline - Utils.monotime
146
+ if budget < 0
147
+ raise CommunicationTimeout
148
+ end
149
+ IO.select([@io.io], nil, nil, budget)
150
+ end
151
+ end
152
+ resp
153
+ end
154
+
155
+ def with_retry
156
+ try = 1
157
+ begin
158
+ yield
159
+ rescue Seriamp::Error, IOError, SystemCallError => exc
160
+ if try <= retries
161
+ logger&.warn("Error during operation: #{exc.class}: #{exc} - will retry")
162
+ try += 1
163
+ if detect_device?
164
+ @device = nil
165
+ end
166
+ retry
167
+ else
168
+ raise
169
+ end
170
+ end
171
+ end
172
+
173
+ def question(cmd)
174
+ dispatch("!1#{cmd}QSTN\r")
175
+ end
176
+
177
+ def boolean_question(cmd)
178
+ resp = question(cmd)
179
+ if resp.start_with?(cmd)
180
+ case Integer(resp[cmd.length...])
181
+ when 1
182
+ true
183
+ when 0
184
+ false
185
+ else
186
+ raise "Bad response #{resp}"
187
+ end
188
+ end
189
+ end
190
+ end
191
+ end
192
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'optparse'
4
+ require 'logger'
5
+ require 'pp'
6
+ require 'seriamp/utils'
7
+ require 'seriamp/detect'
8
+ require 'seriamp/integra/client'
9
+ require 'seriamp/integra/executor'
10
+
11
+ module Seriamp
12
+ module Integra
13
+ class Cmd
14
+ def initialize(args = ARGV, stdin = STDIN)
15
+ options = {}
16
+ OptionParser.new do |opts|
17
+ opts.banner = "Usage: integra [-d device] command arg..."
18
+
19
+ opts.on("-d", "--device DEVICE", "TTY to use (default autodetect)") do |v|
20
+ options[:device] = v
21
+ end
22
+ end.parse!
23
+
24
+ @options = options
25
+
26
+ @logger = Logger.new(STDERR)
27
+ @client = Integra::Client.new(device: options[:device], logger: @logger)
28
+
29
+ @args = args
30
+ @stdin = stdin
31
+ end
32
+
33
+ attr_reader :args
34
+ attr_reader :stdin
35
+ attr_reader :logger
36
+
37
+ def run
38
+ if args.any?
39
+ run_command(args)
40
+ else
41
+ stdin.each_line do |line|
42
+ line.strip!
43
+ line.sub!(/#.*/, '')
44
+ next if line.empty?
45
+
46
+ run_command(line.strip.split(%r,\s+,))
47
+ end
48
+ end
49
+ end
50
+
51
+ def run_command(args)
52
+ cmd = args.shift
53
+ unless cmd
54
+ raise ArgumentError, "No command given"
55
+ end
56
+
57
+ case cmd
58
+ when 'detect'
59
+ device = Seriamp.detect_device(Integra, *args, logger: logger)
60
+ if device
61
+ puts device
62
+ exit 0
63
+ else
64
+ STDERR.puts("Integra receiver not found")
65
+ exit 3
66
+ end
67
+ else
68
+ executor.run_command(cmd, *args)
69
+ end
70
+ end
71
+
72
+ private
73
+
74
+ attr_reader :client
75
+
76
+ def executor
77
+ @executor ||= Executor.new(client)
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Seriamp
4
+ module Integra
5
+ class Executor
6
+ def initialize(client)
7
+ @client = client
8
+ end
9
+
10
+ attr_reader :client
11
+
12
+ def run_command(cmd, *args)
13
+ cmd = cmd.gsub('_', '-')
14
+ case cmd
15
+ when 'detect'
16
+ device = Seriamp.detect_device(Integra, *args, logger: logger)
17
+ if device
18
+ puts device
19
+ exit 0
20
+ else
21
+ STDERR.puts("Integra receiver not found")
22
+ exit 3
23
+ end
24
+ when 'status'
25
+ pp client.status
26
+ else
27
+ raise ArgumentError, "Unknown command: #{cmd}"
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Seriamp
4
+ module Integra
5
+ module Protocol
6
+ module Constants
7
+
8
+ private
9
+
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'seriamp/integra/protocol/constants'
4
+
5
+ module Seriamp
6
+ module Integra
7
+ module Protocol
8
+ module Methods
9
+ include Constants
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'seriamp/integra/client'