sonamp 0.0.4 → 0.0.7
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/LICENSE +23 -0
- data/bin/sonamp +3 -42
- data/lib/sonamp/client.rb +177 -44
- data/lib/sonamp/cmd.rb +81 -0
- data/lib/sonamp/utils.rb +1 -1
- data/lib/sonamp/version.rb +1 -1
- data/sonamp.gemspec +3 -3
- metadata +6 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: caf6fd82a4c7c254e7c132a7b6865094f6c311bd634a92db6a842ab1c3417624
|
4
|
+
data.tar.gz: 31e699f752afaef597a2f328f1a9d3eaceae3f0de2e898a75b8ca4af00ac7737
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0f1a00b0586c04eaece4df0fc7b0af818a827d9b4767862b7b4a12d840fbe3abe1f12f065197fee3f6b734d24fccceb7701aa8b8dc2ed5847d58dd6858ea06b8
|
7
|
+
data.tar.gz: 736d03293482038f5b8a592d1f9c6bbc47f526159baeec9ca704290995ed7c1f3b7cc04d29fae178dd82992894f81dbcb269a41f520b2e38b3ac699fe82abda2
|
data/LICENSE
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
Copyright (c) 2022 Oleg Pudeyev
|
2
|
+
All rights reserved.
|
3
|
+
|
4
|
+
Redistribution and use in source and binary forms, with or without
|
5
|
+
modification, are permitted provided that the following conditions are met:
|
6
|
+
|
7
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
8
|
+
list of conditions and the following disclaimer.
|
9
|
+
|
10
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
11
|
+
this list of conditions and the following disclaimer in the documentation
|
12
|
+
and/or other materials provided with the distribution.
|
13
|
+
|
14
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
15
|
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
16
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
17
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
18
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
19
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
20
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
21
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
22
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
23
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/bin/sonamp
CHANGED
@@ -1,49 +1,10 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
3
|
begin
|
4
|
-
require 'sonamp'
|
4
|
+
require 'sonamp/cmd'
|
5
5
|
rescue LoadError
|
6
6
|
$: << File.join(File.dirname(__FILE__), '../lib')
|
7
|
-
require 'sonamp'
|
7
|
+
require 'sonamp/cmd'
|
8
8
|
end
|
9
|
-
require 'optparse'
|
10
|
-
require 'logger'
|
11
|
-
require 'sonamp/utils'
|
12
9
|
|
13
|
-
|
14
|
-
OptionParser.new do |opts|
|
15
|
-
opts.banner = "Usage: sonamp [-d device] command arg..."
|
16
|
-
|
17
|
-
opts.on("-d", "--device DEVICE", "TTY to use (default autodetect)") do |v|
|
18
|
-
options[:device] = v
|
19
|
-
end
|
20
|
-
end.parse!
|
21
|
-
|
22
|
-
logger = Logger.new(STDERR)
|
23
|
-
client = Sonamp::Client.new(options[:device], logger: logger)
|
24
|
-
|
25
|
-
cmd = ARGV.shift
|
26
|
-
unless cmd
|
27
|
-
raise ArgumentError, "No command given"
|
28
|
-
end
|
29
|
-
|
30
|
-
include Sonamp::Utils
|
31
|
-
|
32
|
-
case cmd
|
33
|
-
when 'power'
|
34
|
-
zone = ARGV.shift.to_i
|
35
|
-
state = parse_on_off(ARGV.shift)
|
36
|
-
client.set_zone_power(zone, state)
|
37
|
-
when 'zvol'
|
38
|
-
zone = ARGV.shift.to_i
|
39
|
-
volume = ARGV.shift.to_i
|
40
|
-
client.set_zone_volume(zone, volume)
|
41
|
-
when 'cvol'
|
42
|
-
channel = ARGV.shift.to_i
|
43
|
-
volume = ARGV.shift.to_i
|
44
|
-
client.set_channel_volume(channel, volume)
|
45
|
-
when 'status'
|
46
|
-
client.status
|
47
|
-
else
|
48
|
-
raise ArgumentError, "Unknown command: #{cmd}"
|
49
|
-
end
|
10
|
+
Sonamp::Cmd.new(ARGV).run
|
data/lib/sonamp/client.rb
CHANGED
@@ -1,15 +1,51 @@
|
|
1
|
+
require 'timeout'
|
2
|
+
|
1
3
|
module Sonamp
|
2
4
|
class Error < StandardError; end
|
3
5
|
class InvalidCommand < Error; end
|
4
6
|
class NotApplicable < Error; end
|
5
7
|
class UnexpectedResponse < Error; end
|
8
|
+
class CommunicationTimeout < Error; end
|
9
|
+
|
10
|
+
RS232_TIMEOUT = 3
|
11
|
+
DEFAULT_DEVICE_GLOB = '/dev/ttyUSB*'
|
12
|
+
|
13
|
+
module_function def detect_device(*patterns, logger: nil)
|
14
|
+
if patterns.empty?
|
15
|
+
patterns = [DEFAULT_DEVICE_GLOB]
|
16
|
+
end
|
17
|
+
devices = patterns.map do |pattern|
|
18
|
+
Dir.glob(pattern)
|
19
|
+
end.flatten.uniq
|
20
|
+
queue = Queue.new
|
21
|
+
threads = devices.map do |device|
|
22
|
+
Thread.new do
|
23
|
+
Timeout.timeout(RS232_TIMEOUT * 2) do
|
24
|
+
logger&.debug("Trying #{device}")
|
25
|
+
Client.new(device, logger: logger).get_zone_power
|
26
|
+
logger&.debug("Found receiver at #{device}")
|
27
|
+
queue << device
|
28
|
+
end
|
29
|
+
rescue CommunicationTimeout, IOError, SystemCallError => exc
|
30
|
+
logger&.debug("Failed on #{device}: #{exc.class}: #{exc}")
|
31
|
+
end
|
32
|
+
end
|
33
|
+
wait_thread = Thread.new do
|
34
|
+
threads.map(&:join)
|
35
|
+
queue << nil
|
36
|
+
end
|
37
|
+
queue.shift.tap do
|
38
|
+
threads.map(&:kill)
|
39
|
+
wait_thread.kill
|
40
|
+
end
|
41
|
+
end
|
6
42
|
|
7
43
|
class Client
|
8
44
|
def initialize(device = nil, logger: nil)
|
9
45
|
@logger = logger
|
10
46
|
|
11
47
|
if device.nil?
|
12
|
-
device =
|
48
|
+
device = Sonamp.detect_device(logger: logger)
|
13
49
|
if device
|
14
50
|
logger&.info("Using #{device} as TTY device")
|
15
51
|
end
|
@@ -39,29 +75,22 @@ module Sonamp
|
|
39
75
|
end
|
40
76
|
|
41
77
|
def get_zone_volume(zone = nil)
|
42
|
-
|
43
|
-
if zone < 1 || zone > 4
|
44
|
-
raise ArgumentError, "Zone must be between 1 and 4: #{zone}"
|
45
|
-
end
|
46
|
-
resp = dispatch(":V#{zone}?")
|
47
|
-
resp[2...].to_i
|
48
|
-
else
|
49
|
-
dispatch(":VG?", 4).map do |resp|
|
50
|
-
resp[2...].to_i
|
51
|
-
end
|
52
|
-
end
|
78
|
+
get_zone_value('V', zone)
|
53
79
|
end
|
54
80
|
|
55
81
|
def set_zone_volume(zone, volume)
|
56
|
-
if zone < 1 || zone > 4
|
57
|
-
raise ArgumentError, "Zone must be between 1 and 4: #{zone}"
|
58
|
-
end
|
59
82
|
if volume < 0 || volume > 100
|
60
83
|
raise ArgumentError, "Volume must be between 0 and 100: #{volume}"
|
61
84
|
end
|
62
|
-
|
63
|
-
|
64
|
-
|
85
|
+
set_zone_value('V', zone, volume)
|
86
|
+
end
|
87
|
+
|
88
|
+
def set_zone_mute(zone, state)
|
89
|
+
set_zone_value('M', zone, state ? 1 : 0)
|
90
|
+
end
|
91
|
+
|
92
|
+
def get_channel_volume(channel = nil)
|
93
|
+
get_channel_value('VC', channel)
|
65
94
|
end
|
66
95
|
|
67
96
|
def set_channel_volume(channel, volume)
|
@@ -76,10 +105,26 @@ module Sonamp
|
|
76
105
|
dispatch_assert(cmd, expected)
|
77
106
|
end
|
78
107
|
|
108
|
+
def set_channel_mute(channel, state)
|
109
|
+
set_channel_value('MC', channel, state ? 1 : 0)
|
110
|
+
end
|
111
|
+
|
79
112
|
def get_zone_mute(zone = nil)
|
80
113
|
get_zone_state('M', zone)
|
81
114
|
end
|
82
115
|
|
116
|
+
def get_channel_mute(channel = nil)
|
117
|
+
get_channel_state('MC', channel)
|
118
|
+
end
|
119
|
+
|
120
|
+
def get_channel_front_panel_level(channel = nil)
|
121
|
+
get_channel_value('TVL', channel)
|
122
|
+
end
|
123
|
+
|
124
|
+
def get_zone_fault(zone = nil)
|
125
|
+
get_zone_state('FP', zone)
|
126
|
+
end
|
127
|
+
|
83
128
|
def get_bbe(zone = nil)
|
84
129
|
get_zone_state('BP', zone)
|
85
130
|
end
|
@@ -100,25 +145,34 @@ module Sonamp
|
|
100
145
|
get_zone_state('VTI', zone)
|
101
146
|
end
|
102
147
|
|
148
|
+
def get_firmware_version
|
149
|
+
global_query('VER')
|
150
|
+
end
|
151
|
+
|
152
|
+
def get_temperature
|
153
|
+
Integer(global_query('TP'))
|
154
|
+
end
|
155
|
+
|
103
156
|
def status
|
104
157
|
# Reusing the opened device file makes :VTIG? fail even with a delay
|
105
158
|
# in front.
|
106
159
|
#open_device do
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
160
|
+
{
|
161
|
+
firmware_version: get_firmware_version,
|
162
|
+
temperature: get_temperature,
|
163
|
+
zone_power: get_zone_power,
|
164
|
+
zone_fault: get_zone_fault,
|
165
|
+
zone_volume: get_zone_volume,
|
166
|
+
channel_volume: get_channel_volume,
|
167
|
+
zone_mute: get_zone_mute,
|
168
|
+
channel_mute: get_channel_mute,
|
169
|
+
bbe: get_bbe,
|
170
|
+
bbe_high_boost: get_bbe_high_boost,
|
171
|
+
bbe_low_boost: get_bbe_low_boost,
|
172
|
+
auto_trigger_input: get_auto_trigger_input,
|
173
|
+
voltage_trigger_input: get_voltage_trigger_input,
|
174
|
+
channel_front_panel_level: get_channel_front_panel_level,
|
175
|
+
}
|
122
176
|
#end
|
123
177
|
end
|
124
178
|
|
@@ -128,7 +182,7 @@ module Sonamp
|
|
128
182
|
if @f
|
129
183
|
yield
|
130
184
|
else
|
131
|
-
File.open(device, 'r+') do |f|
|
185
|
+
File.open(device, 'r+b') do |f|
|
132
186
|
@f = f
|
133
187
|
yield.tap do
|
134
188
|
@f = nil
|
@@ -139,7 +193,9 @@ module Sonamp
|
|
139
193
|
|
140
194
|
def dispatch(cmd, resp_lines_count = 1)
|
141
195
|
open_device do
|
142
|
-
|
196
|
+
with_timeout do
|
197
|
+
@f.syswrite("#{cmd}\x0d")
|
198
|
+
end
|
143
199
|
resp = 1.upto(resp_lines_count).map do
|
144
200
|
read_line(@f, cmd)
|
145
201
|
end
|
@@ -158,28 +214,105 @@ module Sonamp
|
|
158
214
|
end
|
159
215
|
end
|
160
216
|
|
217
|
+
def extract_suffix(resp, expected_prefix)
|
218
|
+
unless resp[0..expected_prefix.length-1] == expected_prefix
|
219
|
+
raise UnexpectedResponse, "Unexpected response: expected #{expected_prefix}..., actual #{resp}"
|
220
|
+
end
|
221
|
+
resp[expected_prefix.length..]
|
222
|
+
end
|
223
|
+
|
224
|
+
def dispatch_extract_suffix(cmd, expected_prefix)
|
225
|
+
resp = dispatch(cmd)
|
226
|
+
extract_suffix(resp, expected_prefix)
|
227
|
+
end
|
228
|
+
|
229
|
+
def global_query(cmd)
|
230
|
+
dispatch_extract_suffix(":#{cmd}?", cmd)
|
231
|
+
end
|
232
|
+
|
233
|
+
def with_timeout(&block)
|
234
|
+
Timeout.timeout(RS232_TIMEOUT, CommunicationTimeout, &block)
|
235
|
+
end
|
236
|
+
|
161
237
|
def read_line(f, cmd)
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
238
|
+
with_timeout do
|
239
|
+
f.readline.strip.tap do |resp|
|
240
|
+
if resp == 'ERR'
|
241
|
+
raise InvalidCommand, "Invalid command: #{cmd}"
|
242
|
+
elsif resp == 'N/A'
|
243
|
+
raise NotApplicable, "Command was recognized but could not be executed - is serial control enabled on the amplifier?"
|
244
|
+
end
|
167
245
|
end
|
168
246
|
end
|
169
247
|
end
|
170
248
|
|
171
|
-
def
|
249
|
+
def set_zone_value(cmd_prefix, zone, value)
|
250
|
+
if zone < 1 || zone > 4
|
251
|
+
raise ArgumentError, "Zone must be between 1 and 4: #{zone}"
|
252
|
+
end
|
253
|
+
cmd = ":#{cmd_prefix}#{zone}#{value}"
|
254
|
+
expected = cmd[1...cmd.length]
|
255
|
+
dispatch_assert(cmd, expected)
|
256
|
+
end
|
257
|
+
|
258
|
+
def get_zone_value(cmd_prefix, zone, boolize: false)
|
172
259
|
if zone
|
173
260
|
if zone < 1 || zone > 4
|
174
261
|
raise ArgumentError, "Zone must be between 1 and 4: #{zone}"
|
175
262
|
end
|
176
263
|
resp = dispatch(":#{cmd_prefix}#{zone}?")
|
177
|
-
resp[cmd_prefix.length + 1]
|
264
|
+
typecast_value(resp[cmd_prefix.length + 1..], boolize)
|
178
265
|
else
|
179
|
-
|
180
|
-
|
266
|
+
index = 1
|
267
|
+
hashize_query_result(dispatch(":#{cmd_prefix}G?", 4), cmd_prefix, boolize)
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
def hashize_query_result(resp_lines, cmd_prefix, boolize)
|
272
|
+
index = 1
|
273
|
+
Hash[resp_lines.map do |resp|
|
274
|
+
value = typecast_value(extract_suffix(resp, "#{cmd_prefix}#{index}"), boolize)
|
275
|
+
[index, value].tap do
|
276
|
+
index += 1
|
181
277
|
end
|
278
|
+
end]
|
279
|
+
end
|
280
|
+
|
281
|
+
def get_zone_state(cmd_prefix, zone)
|
282
|
+
get_zone_value(cmd_prefix, zone, boolize: true)
|
283
|
+
end
|
284
|
+
|
285
|
+
def set_channel_value(cmd_prefix, channel, value)
|
286
|
+
if channel < 1 || channel > 8
|
287
|
+
raise ArgumentError, "Channel must be between 1 and 8: #{channel}"
|
288
|
+
end
|
289
|
+
cmd = ":#{cmd_prefix}#{channel}#{value}"
|
290
|
+
expected = cmd[1...cmd.length]
|
291
|
+
dispatch_assert(cmd, expected)
|
292
|
+
end
|
293
|
+
|
294
|
+
def get_channel_value(cmd_prefix, channel, boolize: false)
|
295
|
+
if channel
|
296
|
+
if channel < 1 || channel > 8
|
297
|
+
raise ArgumentError, "Channel must be between 1 and 8: #{channel}"
|
298
|
+
end
|
299
|
+
typecast_value(dispatch_extract_suffix(":#{cmd_prefix}#{channel}?", "#{cmd_prefix}#{channel}"), boolize)
|
300
|
+
else
|
301
|
+
index = 1
|
302
|
+
hashize_query_result(dispatch(":#{cmd_prefix}G?", 8), cmd_prefix, boolize)
|
303
|
+
end
|
304
|
+
end
|
305
|
+
|
306
|
+
def get_channel_state(cmd_prefix, channel)
|
307
|
+
get_channel_value(cmd_prefix, channel, boolize: true)
|
308
|
+
end
|
309
|
+
|
310
|
+
def typecast_value(value, boolize)
|
311
|
+
value = Integer(value)
|
312
|
+
if boolize
|
313
|
+
value = value == 1
|
182
314
|
end
|
315
|
+
value
|
183
316
|
end
|
184
317
|
end
|
185
318
|
end
|
data/lib/sonamp/cmd.rb
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
require 'logger'
|
3
|
+
require 'sonamp/utils'
|
4
|
+
require 'sonamp/client'
|
5
|
+
|
6
|
+
module Sonamp
|
7
|
+
class Cmd
|
8
|
+
def initialize(args)
|
9
|
+
options = {}
|
10
|
+
OptionParser.new do |opts|
|
11
|
+
opts.banner = "Usage: sonamp [-d device] command arg..."
|
12
|
+
|
13
|
+
opts.on("-d", "--device DEVICE", "TTY to use (default autodetect)") do |v|
|
14
|
+
options[:device] = v
|
15
|
+
end
|
16
|
+
end.parse!(args)
|
17
|
+
|
18
|
+
@options = options
|
19
|
+
|
20
|
+
@logger = Logger.new(STDERR)
|
21
|
+
@client = Sonamp::Client.new(options[:device], logger: @logger)
|
22
|
+
|
23
|
+
@args = args
|
24
|
+
end
|
25
|
+
|
26
|
+
attr_reader :args
|
27
|
+
attr_reader :logger
|
28
|
+
|
29
|
+
def run
|
30
|
+
cmd = args.shift
|
31
|
+
unless cmd
|
32
|
+
raise ArgumentError, "No command given"
|
33
|
+
end
|
34
|
+
|
35
|
+
case cmd
|
36
|
+
when 'detect'
|
37
|
+
device = Sonamp.detect_device(*args, logger: logger)
|
38
|
+
if device
|
39
|
+
puts device
|
40
|
+
exit 0
|
41
|
+
else
|
42
|
+
STDERR.puts("Sonamp amplifier not found")
|
43
|
+
exit 3
|
44
|
+
end
|
45
|
+
when 'off'
|
46
|
+
client.set_zone_power(1, false)
|
47
|
+
client.set_zone_power(2, false)
|
48
|
+
client.set_zone_power(3, false)
|
49
|
+
client.set_zone_power(4, false)
|
50
|
+
when 'power'
|
51
|
+
zone = args.shift.to_i
|
52
|
+
state = Utils.parse_on_off(ARGV.shift)
|
53
|
+
client.set_zone_power(zone, state)
|
54
|
+
when 'zvol'
|
55
|
+
zone = args.shift.to_i
|
56
|
+
volume = ARGV.shift.to_i
|
57
|
+
client.set_zone_volume(zone, volume)
|
58
|
+
when 'cvol'
|
59
|
+
channel = args.shift.to_i
|
60
|
+
volume = args.shift.to_i
|
61
|
+
client.set_channel_volume(channel, volume)
|
62
|
+
when 'zmute'
|
63
|
+
zone = args.shift.to_i
|
64
|
+
mute = ARGV.shift.to_i
|
65
|
+
client.set_zone_mute(zone, mute)
|
66
|
+
when 'cmute'
|
67
|
+
channel = args.shift.to_i
|
68
|
+
mute = args.shift.to_i
|
69
|
+
client.set_channel_mute(channel, mute)
|
70
|
+
when 'status'
|
71
|
+
pp client.status
|
72
|
+
else
|
73
|
+
raise ArgumentError, "Unknown command: #{cmd}"
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
attr_reader :client
|
80
|
+
end
|
81
|
+
end
|
data/lib/sonamp/utils.rb
CHANGED
data/lib/sonamp/version.rb
CHANGED
data/sonamp.gemspec
CHANGED
@@ -2,15 +2,15 @@
|
|
2
2
|
|
3
3
|
Gem::Specification.new do |spec|
|
4
4
|
spec.name = "sonamp"
|
5
|
-
spec.version = '0.0.
|
5
|
+
spec.version = '0.0.7'
|
6
6
|
spec.authors = ['Oleg Pudeyev']
|
7
7
|
spec.email = ['code@olegp.name']
|
8
8
|
spec.summary = %q{Sonance Sonamp Amplifier Serial Control Interface}
|
9
|
-
spec.description = %q{Library for controlling Sonance Sonamp 875D & 875D MkII amplifiers via the serial port}
|
9
|
+
spec.description = %q{Library for controlling Sonance Sonamp 875D SE & 875D MkII amplifiers via the serial port}
|
10
10
|
spec.homepage = "https://github.com/p/sonamp-ruby"
|
11
11
|
spec.license = "MIT"
|
12
12
|
|
13
|
-
spec.files = `git ls-files -z`.split("\x0")
|
13
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |path| path.start_with?('docs/') }
|
14
14
|
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
15
15
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
16
16
|
spec.require_paths = ["lib"]
|
metadata
CHANGED
@@ -1,17 +1,17 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sonamp
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.7
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Oleg Pudeyev
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-11-07 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
|
-
description: Library for controlling Sonance Sonamp 875D & 875D MkII amplifiers
|
14
|
-
the serial port
|
13
|
+
description: Library for controlling Sonance Sonamp 875D SE & 875D MkII amplifiers
|
14
|
+
via the serial port
|
15
15
|
email:
|
16
16
|
- code@olegp.name
|
17
17
|
executables:
|
@@ -21,11 +21,13 @@ extensions: []
|
|
21
21
|
extra_rdoc_files: []
|
22
22
|
files:
|
23
23
|
- ".gitignore"
|
24
|
+
- LICENSE
|
24
25
|
- bin/sonamp
|
25
26
|
- bin/sonamp-web
|
26
27
|
- lib/sonamp.rb
|
27
28
|
- lib/sonamp/app.rb
|
28
29
|
- lib/sonamp/client.rb
|
30
|
+
- lib/sonamp/cmd.rb
|
29
31
|
- lib/sonamp/utils.rb
|
30
32
|
- lib/sonamp/version.rb
|
31
33
|
- sonamp.gemspec
|