onkyo_eiscp_ruby 2.0.1 → 2.1.1
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 +5 -5
- data/README.md +3 -1
- data/VERSION +1 -1
- data/bin/mock_receiver.rb +3 -1
- data/bin/onkyo.rb +20 -24
- data/bin/onkyo_server.rb +1 -0
- data/lib/eiscp.rb +3 -1
- data/lib/eiscp/dictionary.rb +6 -6
- data/lib/eiscp/dictionary/dictionary_generators.rb +17 -21
- data/lib/eiscp/dictionary/dictionary_helpers.rb +24 -26
- data/lib/eiscp/message.rb +11 -11
- data/lib/eiscp/parser.rb +3 -1
- data/lib/eiscp/parser/dynamic_value_parser.rb +2 -0
- data/lib/eiscp/parser/eiscp_parser.rb +3 -2
- data/lib/eiscp/parser/human_readable_parser.rb +10 -10
- data/lib/eiscp/parser/iscp_parser.rb +4 -3
- data/lib/eiscp/receiver.rb +25 -32
- data/lib/eiscp/receiver/command_methods.rb +7 -7
- data/lib/eiscp/receiver/discovery.rb +10 -11
- data/onkyo_eiscp_ruby.gemspec +8 -7
- data/test/tc_dictionary.rb +2 -0
- data/test/tc_message.rb +2 -0
- data/test/tc_parser.rb +2 -1
- data/test/tc_receiver.rb +2 -0
- metadata +4 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
|
-
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: a4b06cd590c97b2221cbb2aea133003a16d59c0ad917c48e3cba7ee0758e9c88
|
|
4
|
+
data.tar.gz: a0eae4abe4ec00ea74db045b4218b6e7adc86f4aefa839e9faa53eee0d135598
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7b2274ea6e68eb2405c32aa4db290621a49bddfdb4b8550d76da52013981691c783b9b03088e6fde4f845ab18a5d858a3bf145cf4a7d60e2ae4531bb4fcecfa4
|
|
7
|
+
data.tar.gz: bd9fa98a349f0eee2dfb80f7c46c8f8ae5ba0a624f6563bd43983cc2d9cf531f0a57095f2db0eb5d7b68232b97bdbda25f1585930b858c4177af0c5a3d2a96f0
|
data/README.md
CHANGED
|
@@ -5,7 +5,9 @@ onkyo_eiscp_ruby
|
|
|
5
5
|
|
|
6
6
|
*A Ruby implementation of eISCP for controlling Onkyo receivers.*
|
|
7
7
|
|
|
8
|
-
**This code is still under
|
|
8
|
+
**This code is not really still under development and using it might still make you sick.**
|
|
9
|
+
|
|
10
|
+
**The python version linked below sees much more activity.**
|
|
9
11
|
|
|
10
12
|
Automatically discover receivers in the broadcast domain
|
|
11
13
|
|
data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
2.
|
|
1
|
+
2.1.1
|
data/bin/mock_receiver.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'socket'
|
|
2
4
|
require_relative './receiver'
|
|
3
5
|
require_relative './message'
|
|
@@ -9,7 +11,7 @@ module EISCP
|
|
|
9
11
|
#
|
|
10
12
|
class MockReceiver
|
|
11
13
|
DISCOVERY_IP = '255.255.255.255'
|
|
12
|
-
ONKYO_DISCOVERY_RESPONSE =
|
|
14
|
+
ONKYO_DISCOVERY_RESPONSE = Message.new('ECN', "TX-NR609/60128/DX/14DAE9E967C8\x19\r\n\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00")
|
|
13
15
|
|
|
14
16
|
# Create/start the server object.
|
|
15
17
|
|
data/bin/onkyo.rb
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
2
3
|
|
|
3
4
|
require 'eiscp'
|
|
4
5
|
require 'optparse'
|
|
@@ -7,14 +8,13 @@ require 'ostruct'
|
|
|
7
8
|
# This object parses ARGV and returns an @option hash
|
|
8
9
|
#
|
|
9
10
|
class Options
|
|
10
|
-
DEFAULT_OPTIONS = { verbose: true, all: false }
|
|
11
|
+
DEFAULT_OPTIONS = { verbose: true, all: false }.freeze
|
|
11
12
|
USAGE = ' Usage: onkyo_rb [options]'
|
|
12
13
|
|
|
13
14
|
def self.parse(args)
|
|
14
15
|
@options = OpenStruct.new
|
|
15
16
|
|
|
16
17
|
options = OptionParser.new do |opts|
|
|
17
|
-
|
|
18
18
|
opts.banner = USAGE
|
|
19
19
|
|
|
20
20
|
opts.on '-d', '--discover', 'Find Onkyo Receivers on the local broadcast domain' do |d|
|
|
@@ -38,17 +38,16 @@ class Options
|
|
|
38
38
|
end
|
|
39
39
|
|
|
40
40
|
opts.on '-m', '--monitor', 'Connect to the first discovered reciever and monitor updates' do |m|
|
|
41
|
-
@options.monitor = m
|
|
41
|
+
@options.monitor = m
|
|
42
42
|
end
|
|
43
|
-
|
|
44
43
|
end
|
|
45
44
|
|
|
46
45
|
options.parse!(args)
|
|
47
46
|
|
|
48
|
-
if @options.nil? && ARGV == []
|
|
47
|
+
puts options if @options.nil? && ARGV == []
|
|
49
48
|
|
|
50
49
|
if @options.discover
|
|
51
|
-
EISCP::Receiver.discover.each {|rec| puts "#{rec.host}:#{rec.port} - #{rec.model} - #{rec.mac_address}"}
|
|
50
|
+
EISCP::Receiver.discover.each { |rec| puts "#{rec.host}:#{rec.port} - #{rec.model} - #{rec.mac_address}" }
|
|
52
51
|
exit 0
|
|
53
52
|
end
|
|
54
53
|
|
|
@@ -59,7 +58,7 @@ class Options
|
|
|
59
58
|
|
|
60
59
|
if @options.monitor
|
|
61
60
|
begin
|
|
62
|
-
rec = EISCP::Receiver.new do |reply|
|
|
61
|
+
rec = EISCP::Receiver.new do |reply|
|
|
63
62
|
puts "#{Time.now} #{rec.host} "\
|
|
64
63
|
"#{reply.zone}: "\
|
|
65
64
|
"#{reply.command_description || reply.command} "\
|
|
@@ -67,9 +66,9 @@ class Options
|
|
|
67
66
|
end
|
|
68
67
|
rec.thread.join
|
|
69
68
|
rescue Interrupt
|
|
70
|
-
|
|
69
|
+
raise 'Exiting...'
|
|
71
70
|
rescue Exception => e
|
|
72
|
-
puts
|
|
71
|
+
puts 'bummer...'
|
|
73
72
|
puts e
|
|
74
73
|
end
|
|
75
74
|
end
|
|
@@ -79,28 +78,25 @@ class Options
|
|
|
79
78
|
modelsets = []
|
|
80
79
|
EISCP::Receiver.discover.each do |rec|
|
|
81
80
|
models << rec.model
|
|
82
|
-
end
|
|
81
|
+
end
|
|
83
82
|
models.each do |model|
|
|
84
83
|
EISCP::Dictionary.modelsets.each do |modelset, list|
|
|
85
|
-
|
|
86
|
-
modelsets << modelset
|
|
87
|
-
end
|
|
84
|
+
modelsets << modelset unless list.select { |x| x.match model }.empty?
|
|
88
85
|
end
|
|
89
86
|
end
|
|
90
87
|
EISCP::Dictionary.zones.each do |zone|
|
|
91
88
|
EISCP::Dictionary.commands[zone].each do |command, command_hash|
|
|
92
|
-
puts
|
|
89
|
+
puts 'Command - Description'
|
|
93
90
|
puts "\n"
|
|
94
91
|
puts " '#{Dictionary.command_to_name(command)}' - "\
|
|
95
92
|
"#{Dictionary.description_from_command(command)}"
|
|
96
93
|
puts "\n"
|
|
97
|
-
puts
|
|
94
|
+
puts ' Value - Description>'
|
|
98
95
|
puts "\n"
|
|
99
|
-
command_hash[:values].each do |
|
|
100
|
-
if
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
else
|
|
96
|
+
command_hash[:values].each do |_value, attr_hash|
|
|
97
|
+
if modelsets.include? attr_hash[:models]
|
|
98
|
+
puts " '#{attr_hash[:name]}' - "\
|
|
99
|
+
" #{attr_hash[:description]}"
|
|
104
100
|
end
|
|
105
101
|
end
|
|
106
102
|
puts "\n"
|
|
@@ -111,14 +107,14 @@ class Options
|
|
|
111
107
|
if @options.list_all
|
|
112
108
|
EISCP::Dictionary.zones.each do |zone|
|
|
113
109
|
EISCP::Dictionary.commands[zone].each do |command, command_hash|
|
|
114
|
-
puts
|
|
110
|
+
puts 'Command - Description'
|
|
115
111
|
puts "\n"
|
|
116
112
|
puts " '#{Dictionary.command_to_name(command)}' - "\
|
|
117
113
|
"#{Dictionary.description_from_command(command)}"
|
|
118
114
|
puts "\n"
|
|
119
|
-
puts
|
|
115
|
+
puts ' Value - Description>'
|
|
120
116
|
puts "\n"
|
|
121
|
-
command_hash[:values].each do |
|
|
117
|
+
command_hash[:values].each do |_value, attr_hash|
|
|
122
118
|
puts " '#{attr_hash[:name]}' - "\
|
|
123
119
|
" #{attr_hash[:description]}"
|
|
124
120
|
end
|
|
@@ -143,7 +139,7 @@ receiver = EISCP::Receiver.discover[0]
|
|
|
143
139
|
receiver.connect
|
|
144
140
|
begin
|
|
145
141
|
command = EISCP::Parser.parse(ARGV.join(' '))
|
|
146
|
-
rescue
|
|
142
|
+
rescue StandardError
|
|
147
143
|
raise "Couldn't parse command"
|
|
148
144
|
end
|
|
149
145
|
reply = receiver.send_recv(command)
|
data/bin/onkyo_server.rb
CHANGED
data/lib/eiscp.rb
CHANGED
data/lib/eiscp/dictionary.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require_relative './dictionary/dictionary_generators'
|
|
2
4
|
require_relative './dictionary/dictionary_helpers'
|
|
3
5
|
|
|
@@ -18,7 +20,7 @@ module EISCP
|
|
|
18
20
|
end
|
|
19
21
|
|
|
20
22
|
DEFAULT_ZONE = 'main'
|
|
21
|
-
@yaml_file_path = File.join(
|
|
23
|
+
@yaml_file_path = File.join(__dir__, '../../eiscp-commands.yaml')
|
|
22
24
|
@commands = YAML.load(File.read(@yaml_file_path))
|
|
23
25
|
@modelsets = @commands[:modelsets]
|
|
24
26
|
@commands.delete(:modelsets)
|
|
@@ -44,11 +46,9 @@ module EISCP
|
|
|
44
46
|
end
|
|
45
47
|
|
|
46
48
|
@additions.each do |zone, command, value, hash|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
puts "Failed to add #{hash} to #{zone}:#{command}:#{value}"
|
|
51
|
-
end
|
|
49
|
+
@commands[zone][command][:values].merge! hash
|
|
50
|
+
rescue StandardError
|
|
51
|
+
puts "Failed to add #{hash} to #{zone}:#{command}:#{value}"
|
|
52
52
|
end
|
|
53
53
|
end
|
|
54
54
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'yaml'
|
|
2
4
|
|
|
3
5
|
module EISCP
|
|
@@ -19,14 +21,12 @@ module EISCP
|
|
|
19
21
|
range.each do |number|
|
|
20
22
|
tmp.merge!(number.to_s(16).rjust(2, '0').upcase =>
|
|
21
23
|
{
|
|
22
|
-
:
|
|
23
|
-
:description
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
}
|
|
27
|
-
)
|
|
24
|
+
name: number.to_s,
|
|
25
|
+
description: @commands[zone][command][:values][value][:description].gsub(/\d - \d+/, number.to_s),
|
|
26
|
+
models: @commands[zone][command][:values][value][:models]
|
|
27
|
+
})
|
|
28
28
|
end
|
|
29
|
-
|
|
29
|
+
tmp
|
|
30
30
|
end
|
|
31
31
|
|
|
32
32
|
# Creates hash object for treble and bass commands
|
|
@@ -36,14 +36,12 @@ module EISCP
|
|
|
36
36
|
['-A', '-8', '-6', '-4', '-2', '00', '+2', '+4', '+6', '+8', '+A'].each do |v|
|
|
37
37
|
tmp.merge!((value[0] + v.to_s) =>
|
|
38
38
|
{
|
|
39
|
-
:
|
|
40
|
-
:description
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}
|
|
44
|
-
)
|
|
39
|
+
name: value[0].downcase + v,
|
|
40
|
+
description: @commands[zone][command][:values][value[0] + '{xx}'][:description].gsub(/\(.*[\]|\)]$/, v),
|
|
41
|
+
models: @commands[zone][command][:values][value[0] + '{xx}'][:models]
|
|
42
|
+
})
|
|
45
43
|
end
|
|
46
|
-
|
|
44
|
+
tmp
|
|
47
45
|
end
|
|
48
46
|
|
|
49
47
|
# Creates hash object for balance commands
|
|
@@ -53,14 +51,12 @@ module EISCP
|
|
|
53
51
|
['-A', '-8', '-6', '-4', '-2', '00', '+2', '+4', '+6', '+8', '+A'].each do |v|
|
|
54
52
|
tmp.merge!(v.to_s =>
|
|
55
53
|
{
|
|
56
|
-
:
|
|
57
|
-
:description
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
}
|
|
61
|
-
)
|
|
54
|
+
name: v.downcase,
|
|
55
|
+
description: @commands[zone][command][:values]['{xx}'][:description].gsub(/\(.*[\]|\)]$/, v),
|
|
56
|
+
models: @commands[zone][command][:values]['{xx}'][:models]
|
|
57
|
+
})
|
|
62
58
|
end
|
|
63
|
-
|
|
59
|
+
tmp
|
|
64
60
|
end
|
|
65
61
|
end
|
|
66
62
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module EISCP
|
|
2
4
|
module Dictionary
|
|
3
5
|
# This module provides methods to get information from the Dictionary about
|
|
@@ -19,9 +21,9 @@ module EISCP
|
|
|
19
21
|
command = command.upcase
|
|
20
22
|
begin
|
|
21
23
|
zone = zone_from_command(command)
|
|
22
|
-
|
|
23
|
-
rescue
|
|
24
|
-
|
|
24
|
+
@commands[zone][command][:name]
|
|
25
|
+
rescue StandardError
|
|
26
|
+
nil
|
|
25
27
|
end
|
|
26
28
|
end
|
|
27
29
|
|
|
@@ -34,46 +36,45 @@ module EISCP
|
|
|
34
36
|
return command if attrs[:name] == name
|
|
35
37
|
end
|
|
36
38
|
end
|
|
37
|
-
|
|
39
|
+
nil
|
|
38
40
|
|
|
39
41
|
else
|
|
40
42
|
|
|
41
43
|
@commands[command_zone].each_pair do |command, attrs|
|
|
42
44
|
return command if attrs[:name] == name
|
|
43
45
|
end
|
|
44
|
-
|
|
46
|
+
nil
|
|
45
47
|
|
|
46
48
|
end
|
|
47
49
|
end
|
|
48
50
|
|
|
49
51
|
# Return a value name from a command and a value
|
|
50
52
|
def command_value_to_value_name(command, value)
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
nil
|
|
56
|
-
end
|
|
53
|
+
zone = zone_from_command(command)
|
|
54
|
+
@commands[zone][command][:values][value][:name]
|
|
55
|
+
rescue StandardError
|
|
56
|
+
nil
|
|
57
57
|
end
|
|
58
58
|
|
|
59
59
|
# Return a value from a command and value name
|
|
60
60
|
def command_value_name_to_value(command, value_name)
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
61
|
+
zone = zone_from_command(command)
|
|
62
|
+
@commands[zone][command][:values].each_pair do |k, v|
|
|
63
|
+
if v[:name].class == String
|
|
64
64
|
return k if v[:name] == value_name.to_s
|
|
65
|
+
elsif v[:name].class == Array
|
|
66
|
+
return k if v[:name].first == value_name.to_s
|
|
65
67
|
end
|
|
66
|
-
rescue
|
|
67
|
-
nil
|
|
68
68
|
end
|
|
69
|
+
return nil
|
|
70
|
+
rescue StandardError
|
|
71
|
+
nil
|
|
69
72
|
end
|
|
70
73
|
|
|
71
74
|
# Return a command description from a command name and zone
|
|
72
75
|
def description_from_command_name(name, zone)
|
|
73
76
|
@commands[zone].each_pair do |command, attrs|
|
|
74
|
-
if attrs[:name] == name
|
|
75
|
-
return @commands[zone][command][:description]
|
|
76
|
-
end
|
|
77
|
+
return @commands[zone][command][:description] if attrs[:name] == name
|
|
77
78
|
end
|
|
78
79
|
nil
|
|
79
80
|
end
|
|
@@ -105,14 +106,11 @@ module EISCP
|
|
|
105
106
|
# Checks to see if the command is in the Dictionary
|
|
106
107
|
#
|
|
107
108
|
def known_command?(command)
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
return nil
|
|
113
|
-
end
|
|
109
|
+
zone = zone_from_command(command)
|
|
110
|
+
@commands[zone].include? command
|
|
111
|
+
rescue StandardError
|
|
112
|
+
nil
|
|
114
113
|
end
|
|
115
114
|
end
|
|
116
|
-
|
|
117
115
|
end
|
|
118
116
|
end
|
data/lib/eiscp/message.rb
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
#
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
2
3
|
require_relative './dictionary'
|
|
3
4
|
require_relative './parser'
|
|
4
5
|
|
|
@@ -52,12 +53,12 @@ module EISCP
|
|
|
52
53
|
# @param [String] value variable length ISCP command value
|
|
53
54
|
# @param [String] unit_type_character override default unit type character, optional
|
|
54
55
|
# @param [String] start_character override default start character, optional
|
|
55
|
-
def initialize(command: nil, value: nil, terminator:
|
|
56
|
+
def initialize(command: nil, value: nil, terminator: "\r\n", unit_type: '1', start: '!')
|
|
56
57
|
unless Dictionary.known_command?(command)
|
|
57
|
-
#STDERR.puts "Unknown command #{command}"
|
|
58
|
+
# STDERR.puts "Unknown command #{command}"
|
|
58
59
|
end
|
|
59
60
|
|
|
60
|
-
|
|
61
|
+
raise 'No value specified.' if value.nil?
|
|
61
62
|
|
|
62
63
|
@command = command
|
|
63
64
|
@value = value
|
|
@@ -65,28 +66,27 @@ module EISCP
|
|
|
65
66
|
@unit_type = unit_type
|
|
66
67
|
@start = start
|
|
67
68
|
@header = { magic: MAGIC,
|
|
68
|
-
header_size:
|
|
69
|
+
header_size: HEADER_SIZE,
|
|
69
70
|
data_size: to_iscp.length,
|
|
70
71
|
version: ISCP_VERSION,
|
|
71
|
-
reserved: RESERVED
|
|
72
|
-
}
|
|
72
|
+
reserved: RESERVED }
|
|
73
73
|
begin
|
|
74
74
|
get_human_readable_attrs
|
|
75
|
-
rescue
|
|
76
|
-
#STDERR.puts"Couldn't get all human readable attrs"
|
|
75
|
+
rescue StandardError
|
|
76
|
+
# STDERR.puts"Couldn't get all human readable attrs"
|
|
77
77
|
end
|
|
78
78
|
end
|
|
79
79
|
|
|
80
80
|
# Check if two messages are equivalent comparing their ISCP messages.
|
|
81
81
|
#
|
|
82
82
|
def ==(other)
|
|
83
|
-
to_iscp == other.to_iscp
|
|
83
|
+
to_iscp == other.to_iscp
|
|
84
84
|
end
|
|
85
85
|
|
|
86
86
|
# Return ISCP Message string
|
|
87
87
|
#
|
|
88
88
|
def to_iscp
|
|
89
|
-
|
|
89
|
+
(@start + @unit_type + @command + @value).to_s
|
|
90
90
|
end
|
|
91
91
|
|
|
92
92
|
# Return EISCP Message string
|
data/lib/eiscp/parser.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require_relative './parser/eiscp_parser'
|
|
2
4
|
require_relative './parser/iscp_parser'
|
|
3
5
|
require_relative './parser/human_readable_parser'
|
|
@@ -17,6 +19,6 @@ module EISCP
|
|
|
17
19
|
else
|
|
18
20
|
HumanReadableParser.parse(string)
|
|
19
21
|
end
|
|
20
|
-
end
|
|
22
|
+
end
|
|
21
23
|
end
|
|
22
24
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require_relative '../dictionary'
|
|
2
4
|
|
|
3
5
|
module EISCP
|
|
@@ -8,22 +10,20 @@ module EISCP
|
|
|
8
10
|
def self.parse(string)
|
|
9
11
|
array = string.split(' ')
|
|
10
12
|
|
|
11
|
-
if Dictionary.zones.include? array[0]
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
parsed_zone = if Dictionary.zones.include? array[0]
|
|
14
|
+
array.shift
|
|
15
|
+
else
|
|
16
|
+
Dictionary::DEFAULT_ZONE
|
|
17
|
+
end
|
|
16
18
|
|
|
17
19
|
command_name = array.shift
|
|
18
|
-
value_name = array.join(
|
|
20
|
+
value_name = array.join(' ')
|
|
19
21
|
command = Dictionary.command_name_to_command(command_name, parsed_zone)
|
|
20
22
|
value = Dictionary.command_value_name_to_value(command, value_name)
|
|
21
|
-
if
|
|
22
|
-
|
|
23
|
-
end
|
|
23
|
+
return nil if command.nil? || value.nil?
|
|
24
|
+
|
|
24
25
|
Message.new(command: command, value: value)
|
|
25
26
|
end
|
|
26
|
-
|
|
27
27
|
end
|
|
28
28
|
end
|
|
29
29
|
end
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module EISCP
|
|
2
4
|
module Parser
|
|
3
5
|
# This module parses an ISCP string and returns a Message object
|
|
4
6
|
#
|
|
5
7
|
module ISCPParser
|
|
6
8
|
# Regexp for parsing ISCP messages
|
|
7
|
-
REGEX = /(?<start>!)?(?<unit_type>(\d|x))?(?<command>[A-Z]{3})\s?(?<value>.*?)(?<terminator>[[:cntrl:]]*$)
|
|
9
|
+
REGEX = /(?<start>!)?(?<unit_type>(\d|x))?(?<command>[A-Z]{3})\s?(?<value>.*?)(?<terminator>[[:cntrl:]]*$)/.freeze
|
|
8
10
|
def self.parse(string)
|
|
9
11
|
match = string.match(REGEX)
|
|
10
12
|
|
|
@@ -15,9 +17,8 @@ module EISCP
|
|
|
15
17
|
hash.delete_if { |_, v| v.nil? || v == '' }
|
|
16
18
|
|
|
17
19
|
# Convert keys to symbols
|
|
18
|
-
hash = hash.
|
|
20
|
+
hash = hash.each_with_object({}) do |(k, v), memo|
|
|
19
21
|
memo[k.to_sym] = v
|
|
20
|
-
memo
|
|
21
22
|
end
|
|
22
23
|
|
|
23
24
|
Message.new(**hash)
|
data/lib/eiscp/receiver.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'resolv'
|
|
2
4
|
require_relative './receiver/discovery'
|
|
3
5
|
require_relative './receiver/command_methods'
|
|
@@ -40,7 +42,6 @@ module EISCP
|
|
|
40
42
|
# Default Onkyo eISCP port
|
|
41
43
|
ONKYO_PORT = 60_128
|
|
42
44
|
|
|
43
|
-
|
|
44
45
|
# Create a new EISCP::Receiver object to communicate with a receiver.
|
|
45
46
|
# If no host is given, use auto discovery and create a
|
|
46
47
|
# receiver object using the first host to respond.
|
|
@@ -53,7 +54,7 @@ module EISCP
|
|
|
53
54
|
# with the Message object that results from a CommandMethod being called.
|
|
54
55
|
# All we're doing here is calling #send_recv
|
|
55
56
|
#
|
|
56
|
-
command_method_proc =
|
|
57
|
+
command_method_proc = proc { |msg| send_recv msg }
|
|
57
58
|
CommandMethods.generate(&command_method_proc)
|
|
58
59
|
|
|
59
60
|
# This proc sets the four ECN attributes and initiates a connection to the
|
|
@@ -64,9 +65,7 @@ module EISCP
|
|
|
64
65
|
@port = hash[:port]
|
|
65
66
|
@area = hash[:area]
|
|
66
67
|
@mac_address = hash[:mac_address]
|
|
67
|
-
if block_given?
|
|
68
|
-
connect(&block)
|
|
69
|
-
end
|
|
68
|
+
connect(&block) if block_given?
|
|
70
69
|
end
|
|
71
70
|
|
|
72
71
|
# This lambda sets the host IP after resolving it
|
|
@@ -83,12 +82,11 @@ module EISCP
|
|
|
83
82
|
# Else, use the given host and hash to create a new Receiver object.
|
|
84
83
|
# This is how ::discover creates Receivers.
|
|
85
84
|
#
|
|
86
|
-
|
|
87
|
-
when host.nil?
|
|
85
|
+
if host.nil?
|
|
88
86
|
first_found = Receiver.discover[0]
|
|
89
87
|
set_host.call first_found.host
|
|
90
88
|
set_attrs.call first_found.ecn_hash
|
|
91
|
-
|
|
89
|
+
elsif info_hash.empty?
|
|
92
90
|
set_host.call host
|
|
93
91
|
Receiver.discover.each do |receiver|
|
|
94
92
|
receiver.host == @host && set_attrs.call(receiver.ecn_hash)
|
|
@@ -114,16 +112,14 @@ module EISCP
|
|
|
114
112
|
end
|
|
115
113
|
private :update_thread
|
|
116
114
|
|
|
117
|
-
# This creates a socket conection to the receiver if one doesn't exist,
|
|
115
|
+
# This creates a socket conection to the receiver if one doesn't exist,
|
|
118
116
|
# and updates or sets the callback block if one is passed.
|
|
119
117
|
#
|
|
120
118
|
def connect(&block)
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
puts e
|
|
126
|
-
end
|
|
119
|
+
@socket ||= TCPSocket.new(@host, @port)
|
|
120
|
+
update_thread(&block)
|
|
121
|
+
rescue StandardError => e
|
|
122
|
+
puts e
|
|
127
123
|
end
|
|
128
124
|
|
|
129
125
|
# Disconnect from the receiver by closing the socket and killing the
|
|
@@ -156,9 +152,7 @@ module EISCP
|
|
|
156
152
|
# Sends an EISCP::Message object or string on the network and returns recieved data string.
|
|
157
153
|
#
|
|
158
154
|
def send_recv(eiscp)
|
|
159
|
-
if eiscp.is_a? String
|
|
160
|
-
eiscp = Parser.parse(eiscp)
|
|
161
|
-
end
|
|
155
|
+
eiscp = Parser.parse(eiscp) if eiscp.is_a? String
|
|
162
156
|
send eiscp
|
|
163
157
|
sleep DEFAULT_TIMEOUT
|
|
164
158
|
Parser.parse("#{eiscp.command}#{@state[eiscp.command]}")
|
|
@@ -170,16 +164,15 @@ module EISCP
|
|
|
170
164
|
{ model: @model,
|
|
171
165
|
port: @port,
|
|
172
166
|
area: @area,
|
|
173
|
-
mac_address: @mac_address
|
|
174
|
-
}
|
|
167
|
+
mac_address: @mac_address }
|
|
175
168
|
end
|
|
176
169
|
|
|
177
|
-
# This will return a human-readable represantion of the receiver's state.
|
|
170
|
+
# This will return a human-readable represantion of the receiver's state.
|
|
178
171
|
#
|
|
179
172
|
def human_readable_state
|
|
180
173
|
hash = {}
|
|
181
174
|
@state.each do |c, v|
|
|
182
|
-
hash[
|
|
175
|
+
hash[Dictionary.command_to_name(c).to_s] = (Dictionary.command_value_to_value_name(c, v) || v.to_s).to_s
|
|
183
176
|
end
|
|
184
177
|
hash
|
|
185
178
|
end
|
|
@@ -189,18 +182,18 @@ module EISCP
|
|
|
189
182
|
#
|
|
190
183
|
def update_state
|
|
191
184
|
Thread.new do
|
|
192
|
-
Dictionary.commands.each do |zone,
|
|
185
|
+
Dictionary.commands.each do |zone, _commands|
|
|
193
186
|
Dictionary.commands[zone].each do |command, info|
|
|
194
187
|
info[:values].each do |value, _|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
188
|
+
next unless value == 'QSTN'
|
|
189
|
+
|
|
190
|
+
send(Parser.parse(command + 'QSTN'))
|
|
191
|
+
# If we send any faster we risk making the stereo drop replies.
|
|
192
|
+
# A dropped reply is not necessarily indicative of the
|
|
193
|
+
# receiver's failure to receive the command and change state
|
|
194
|
+
# accordingly. In this case, we're only making queries, so we do
|
|
195
|
+
# want to capture every reply.
|
|
196
|
+
sleep DEFAULT_TIMEOUT
|
|
204
197
|
end
|
|
205
198
|
end
|
|
206
199
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require_relative '../parser'
|
|
2
4
|
require_relative '../dictionary'
|
|
3
5
|
|
|
@@ -12,14 +14,12 @@ module EISCP
|
|
|
12
14
|
def self.generate(&block)
|
|
13
15
|
Dictionary.zones.each do |zone|
|
|
14
16
|
Dictionary.commands[zone].each do |command, _values|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
self.instance_exec Parser.parse(command_name.gsub(/_/, '-') + ' ' + v), &block
|
|
19
|
-
end
|
|
20
|
-
rescue => e
|
|
21
|
-
puts e
|
|
17
|
+
command_name = Dictionary.command_to_name(command).to_s.gsub(/-/, '_')
|
|
18
|
+
define_method(command_name) do |v|
|
|
19
|
+
instance_exec Parser.parse(command_name.gsub(/_/, '-') + ' ' + v), &block
|
|
22
20
|
end
|
|
21
|
+
rescue StandardError => e
|
|
22
|
+
puts e
|
|
23
23
|
end
|
|
24
24
|
end
|
|
25
25
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'socket'
|
|
2
4
|
require_relative '../message'
|
|
3
5
|
require_relative '../parser'
|
|
@@ -31,17 +33,14 @@ module EISCP
|
|
|
31
33
|
sock.send(ONKYO_MAGIC, 0, '<broadcast>', discovery_port)
|
|
32
34
|
data = []
|
|
33
35
|
loop do
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
else
|
|
43
|
-
retry
|
|
44
|
-
end
|
|
36
|
+
msg, addr = sock.recvfrom_nonblock(1024)
|
|
37
|
+
data << Receiver.new(addr[2], ecn_string_to_ecn_array(msg))
|
|
38
|
+
rescue IO::WaitReadable
|
|
39
|
+
io = IO.select([sock], nil, nil, 0.5)
|
|
40
|
+
if io.nil?
|
|
41
|
+
return data
|
|
42
|
+
else
|
|
43
|
+
retry
|
|
45
44
|
end
|
|
46
45
|
end
|
|
47
46
|
end
|
data/onkyo_eiscp_ruby.gemspec
CHANGED
|
@@ -1,28 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
Gem::Specification.new do |s|
|
|
2
4
|
s.name = 'onkyo_eiscp_ruby'
|
|
3
5
|
s.version = File.read(File.expand_path('VERSION', File.dirname(__FILE__))).strip
|
|
4
|
-
s.licenses
|
|
6
|
+
s.licenses = ['MIT']
|
|
5
7
|
s.platform = Gem::Platform::RUBY
|
|
6
8
|
s.summary = 'Manipulate Onkyo stereos with the eISCP protocol'
|
|
7
9
|
s.files = Dir.glob('{bin,config,lib,test,doc}/**/*') +
|
|
8
|
-
|
|
10
|
+
['VERSION', 'onkyo_eiscp_ruby.gemspec', 'eiscp-commands.yaml']
|
|
9
11
|
s.extra_rdoc_files = ['README.md']
|
|
10
12
|
s.require_path = 'lib'
|
|
11
13
|
|
|
12
14
|
s.homepage = 'https://github.com/mikerodrigues/onkyo_eiscp_ruby'
|
|
13
15
|
|
|
14
|
-
s.description =
|
|
16
|
+
s.description = '
|
|
15
17
|
Control Onkyo receivers over the network.Use the provided binary or
|
|
16
18
|
require the library for use in your scripts.
|
|
17
|
-
|
|
19
|
+
'
|
|
18
20
|
|
|
19
21
|
s.author = 'Michael Rodrigues'
|
|
20
22
|
s.email = 'mikebrodrigues@gmail.com'
|
|
21
23
|
|
|
22
24
|
s.test_files = Dir['test/tc*.rb']
|
|
23
|
-
s.executables = %w
|
|
25
|
+
s.executables = %w[
|
|
24
26
|
onkyo.rb
|
|
25
27
|
onkyo_server.rb
|
|
26
|
-
|
|
27
|
-
|
|
28
|
+
]
|
|
28
29
|
end
|
data/test/tc_dictionary.rb
CHANGED
data/test/tc_message.rb
CHANGED
data/test/tc_parser.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require_relative '../lib/eiscp/parser'
|
|
2
4
|
require_relative '../lib/eiscp/message'
|
|
3
5
|
require 'minitest/autorun'
|
|
@@ -29,5 +31,4 @@ class TestParser < MiniTest::Test
|
|
|
29
31
|
def test_return_nil_for_fake_human_readable
|
|
30
32
|
assert_equal(EISCP::Parser.parse('fake-command value'), nil)
|
|
31
33
|
end
|
|
32
|
-
|
|
33
34
|
end
|
data/test/tc_receiver.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: onkyo_eiscp_ruby
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.
|
|
4
|
+
version: 2.1.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Michael Rodrigues
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2021-03-21 00:00:00.000000000 Z
|
|
12
12
|
dependencies: []
|
|
13
13
|
description: "\n Control Onkyo receivers over the network.Use the provided binary
|
|
14
14
|
or\n require the library for use in your scripts.\n "
|
|
@@ -63,13 +63,12 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
63
63
|
- !ruby/object:Gem::Version
|
|
64
64
|
version: '0'
|
|
65
65
|
requirements: []
|
|
66
|
-
|
|
67
|
-
rubygems_version: 2.4.5
|
|
66
|
+
rubygems_version: 3.2.3
|
|
68
67
|
signing_key:
|
|
69
68
|
specification_version: 4
|
|
70
69
|
summary: Manipulate Onkyo stereos with the eISCP protocol
|
|
71
70
|
test_files:
|
|
72
|
-
- test/tc_receiver.rb
|
|
73
71
|
- test/tc_dictionary.rb
|
|
74
72
|
- test/tc_message.rb
|
|
75
73
|
- test/tc_parser.rb
|
|
74
|
+
- test/tc_receiver.rb
|