seriamp 0.1.14 → 0.2.0

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: 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'