onkyo_eiscp_ruby 2.1.1 → 2.1.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +25 -12
- data/VERSION +1 -1
- data/bin/mock_receiver.rb +2 -1
- data/bin/onkyo.rb +3 -3
- data/lib/eiscp/dictionary/dictionary_generators.rb +3 -3
- data/lib/eiscp/dictionary/dictionary_helpers.rb +9 -4
- data/lib/eiscp/dictionary.rb +6 -7
- data/lib/eiscp/message.rb +2 -3
- data/lib/eiscp/parser/eiscp_parser.rb +6 -2
- data/lib/eiscp/parser/iscp_parser.rb +2 -4
- data/lib/eiscp/receiver/command_methods.rb +1 -1
- data/lib/eiscp/receiver/discovery.rb +14 -12
- data/lib/eiscp/receiver.rb +8 -13
- data/lib/eiscp.rb +1 -1
- data/test/tc_dictionary.rb +1 -1
- data/test/tc_message.rb +4 -1
- data/test/tc_parser.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d3dd4eba4824a8b6f5698a427829515653a327f7e7a9a3439b0c66be8ed71ce1
|
4
|
+
data.tar.gz: 5e747f4b17191a38c76067df0461f9a59b7e0e116a78f34c6d8507e1ff06b2f2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c0053cc9c49574d835e967190edeea8519dd99ef25f3c397046274fb3da3d319292719cc5c002f0a839d69d8da271ad274cf4f449cfbab695c8c00acdeb0df42
|
7
|
+
data.tar.gz: 60476141723946c9741f957fea30e3b0709c13907155619e559cd403f6d2abf5c88bd693153cf33a35d47f9f3b07d3444ef14880f641b91d8628e83d9232f812
|
data/README.md
CHANGED
@@ -3,21 +3,24 @@ onkyo_eiscp_ruby
|
|
3
3
|
[![Gem Version](https://badge.fury.io/rb/onkyo_eiscp_ruby.png)](http://badge.fury.io/rb/onkyo_eiscp_ruby)
|
4
4
|
[![GitHub version](https://badge.fury.io/gh/mikerodrigues%2Fonkyo_eiscp_ruby.png)](http://badge.fury.io/gh/mikerodrigues%2Fonkyo_eiscp_ruby)
|
5
5
|
|
6
|
-
*A Ruby implementation of eISCP for controlling Onkyo receivers.*
|
6
|
+
*A Ruby implementation of eISCP (ethernet Integra Serial Control Protocol) for controlling Onkyo receivers.*
|
7
7
|
|
8
|
-
**
|
8
|
+
**I'm still sort of updating this code. Please feel free to reach out if there's something you need that it doesn't do, I may be willing to help.**
|
9
9
|
|
10
10
|
**The python version linked below sees much more activity.**
|
11
11
|
|
12
|
-
Automatically discover receivers in the broadcast domain
|
13
12
|
|
14
|
-
|
13
|
+
Features
|
14
|
+
---------------
|
15
|
+
* Automatically discover receivers in the broadcast domain
|
16
|
+
|
17
|
+
* Send commands to receivers and parse returned messages
|
15
18
|
|
16
|
-
Open a TCP socket to receive solicited and non-solicited status updates.
|
19
|
+
* Open a TCP socket to receive solicited and non-solicited status updates.
|
17
20
|
|
18
|
-
Mock reciever (currently only responds to discovery)
|
21
|
+
* Mock reciever (currently only responds to discovery)
|
19
22
|
|
20
|
-
Human-readable commands
|
23
|
+
* Human-readable commands
|
21
24
|
|
22
25
|
**Inspired by https://github.com/miracle2k/onkyo-eiscp
|
23
26
|
|
@@ -40,7 +43,11 @@ What's missing?
|
|
40
43
|
|
41
44
|
Using the Library
|
42
45
|
-----------------
|
43
|
-
*
|
46
|
+
* Install the library
|
47
|
+
|
48
|
+
gem install onkyo_eiscp_ruby
|
49
|
+
|
50
|
+
* Require the library
|
44
51
|
|
45
52
|
require 'eiscp'
|
46
53
|
|
@@ -49,7 +56,7 @@ Using the Library
|
|
49
56
|
`VERSION`)
|
50
57
|
|
51
58
|
* You can do most everything through the `Receiver` and `Message` objects. If you
|
52
|
-
want to accept user input you will probably want to use the Parser module. Be
|
59
|
+
want to accept user input you will probably want to use the `Parser` module. Be
|
53
60
|
sure to check out the RDocs or dig through the source code. I try to keep it
|
54
61
|
well commented/documented, and there's more functionality to the library than
|
55
62
|
is shown here:
|
@@ -75,7 +82,7 @@ Using the Library
|
|
75
82
|
EISCP::Receiver.discover
|
76
83
|
```
|
77
84
|
|
78
|
-
* Create `Receiver` object from first discovered
|
85
|
+
* Create `Receiver` object from first discovered receiver on the LAN
|
79
86
|
|
80
87
|
```ruby
|
81
88
|
receiver = EISCP::Receiver.new
|
@@ -97,8 +104,9 @@ Using the Library
|
|
97
104
|
end
|
98
105
|
```
|
99
106
|
|
100
|
-
* Receivers created without a block will not connect automatically.
|
101
|
-
|
107
|
+
* `Receivers` created without a block will not connect automatically. If you try
|
108
|
+
to send a command it will connect transparently, otherwise you can use the
|
109
|
+
`connect` method to explicitly open a socket to the receiver.
|
102
110
|
|
103
111
|
```ruby
|
104
112
|
receiver.connect
|
@@ -162,6 +170,11 @@ Using the Library
|
|
162
170
|
|
163
171
|
# Set the master volume to 45
|
164
172
|
receiver.master_volume "45"
|
173
|
+
|
174
|
+
# Change the input to TV/CD
|
175
|
+
# Note: when a command value has more than one name (an array in the YAML file)
|
176
|
+
# we default to using the first entry. So for `['cd', 'tv', 'cd']` you get:
|
177
|
+
receiver.input_selector "cd"
|
165
178
|
```
|
166
179
|
|
167
180
|
* Parse ISCP and human readable strings:
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
2.1.
|
1
|
+
2.1.6
|
data/bin/mock_receiver.rb
CHANGED
@@ -11,7 +11,8 @@ module EISCP
|
|
11
11
|
#
|
12
12
|
class MockReceiver
|
13
13
|
DISCOVERY_IP = '255.255.255.255'
|
14
|
-
ONKYO_DISCOVERY_RESPONSE = Message.new('ECN',
|
14
|
+
ONKYO_DISCOVERY_RESPONSE = Message.new('ECN',
|
15
|
+
"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")
|
15
16
|
|
16
17
|
# Create/start the server object.
|
17
18
|
|
data/bin/onkyo.rb
CHANGED
@@ -95,7 +95,7 @@ class Options
|
|
95
95
|
puts "\n"
|
96
96
|
command_hash[:values].each do |_value, attr_hash|
|
97
97
|
if modelsets.include? attr_hash[:models]
|
98
|
-
puts " '#{
|
98
|
+
puts " '#{EISCP::Dictionary.command_value_to_value_name(command, _value)}' - "\
|
99
99
|
" #{attr_hash[:description]}"
|
100
100
|
end
|
101
101
|
end
|
@@ -115,8 +115,8 @@ class Options
|
|
115
115
|
puts ' Value - Description>'
|
116
116
|
puts "\n"
|
117
117
|
command_hash[:values].each do |_value, attr_hash|
|
118
|
-
puts " '#{
|
119
|
-
|
118
|
+
puts " '#{EISCP::Dictionary.command_value_to_value_name(command, _value)}' - "\
|
119
|
+
" #{attr_hash[:description]}"
|
120
120
|
end
|
121
121
|
puts "\n"
|
122
122
|
end
|
@@ -37,8 +37,8 @@ module EISCP
|
|
37
37
|
tmp.merge!((value[0] + v.to_s) =>
|
38
38
|
{
|
39
39
|
name: value[0].downcase + v,
|
40
|
-
description: @commands[zone][command][:values][value[0]
|
41
|
-
models: @commands[zone][command][:values][value[0]
|
40
|
+
description: @commands[zone][command][:values]["#{value[0]}{xx}"][:description].gsub(/\(.*[\]|)]$/, v),
|
41
|
+
models: @commands[zone][command][:values]["#{value[0]}{xx}"][:models]
|
42
42
|
})
|
43
43
|
end
|
44
44
|
tmp
|
@@ -52,7 +52,7 @@ module EISCP
|
|
52
52
|
tmp.merge!(v.to_s =>
|
53
53
|
{
|
54
54
|
name: v.downcase,
|
55
|
-
description: @commands[zone][command][:values]['{xx}'][:description].gsub(/\(.*[\]
|
55
|
+
description: @commands[zone][command][:values]['{xx}'][:description].gsub(/\(.*[\]|)]$/, v),
|
56
56
|
models: @commands[zone][command][:values]['{xx}'][:models]
|
57
57
|
})
|
58
58
|
end
|
@@ -51,7 +51,12 @@ module EISCP
|
|
51
51
|
# Return a value name from a command and a value
|
52
52
|
def command_value_to_value_name(command, value)
|
53
53
|
zone = zone_from_command(command)
|
54
|
-
@commands[zone][command][:values][value][:name]
|
54
|
+
command_value = @commands[zone][command][:values][value][:name]
|
55
|
+
if command_value.instance_of?(String)
|
56
|
+
command_value
|
57
|
+
elsif command_value.instance_of?(Array)
|
58
|
+
command_value.first
|
59
|
+
end
|
55
60
|
rescue StandardError
|
56
61
|
nil
|
57
62
|
end
|
@@ -60,13 +65,13 @@ module EISCP
|
|
60
65
|
def command_value_name_to_value(command, value_name)
|
61
66
|
zone = zone_from_command(command)
|
62
67
|
@commands[zone][command][:values].each_pair do |k, v|
|
63
|
-
if v[:name].
|
68
|
+
if v[:name].instance_of?(String)
|
64
69
|
return k if v[:name] == value_name.to_s
|
65
|
-
elsif v[:name].
|
70
|
+
elsif v[:name].instance_of?(Array)
|
66
71
|
return k if v[:name].first == value_name.to_s
|
67
72
|
end
|
68
73
|
end
|
69
|
-
|
74
|
+
nil
|
70
75
|
rescue StandardError
|
71
76
|
nil
|
72
77
|
end
|
data/lib/eiscp/dictionary.rb
CHANGED
@@ -14,14 +14,12 @@ module EISCP
|
|
14
14
|
extend DictionaryHelpers
|
15
15
|
|
16
16
|
class << self
|
17
|
-
attr_reader :zones
|
18
|
-
attr_reader :modelsets
|
19
|
-
attr_reader :commands
|
17
|
+
attr_reader :zones, :modelsets, :commands
|
20
18
|
end
|
21
19
|
|
22
20
|
DEFAULT_ZONE = 'main'
|
23
21
|
@yaml_file_path = File.join(__dir__, '../../eiscp-commands.yaml')
|
24
|
-
@commands = YAML.
|
22
|
+
@commands = YAML.safe_load(File.read(@yaml_file_path), permitted_classes: [Symbol])
|
25
23
|
@modelsets = @commands[:modelsets]
|
26
24
|
@commands.delete(:modelsets)
|
27
25
|
@zones = @commands.map { |k, _| k }
|
@@ -32,11 +30,12 @@ module EISCP
|
|
32
30
|
command = command[0]
|
33
31
|
@commands[zone][command][:values].each do |value|
|
34
32
|
value = value[0]
|
35
|
-
|
33
|
+
case value
|
34
|
+
when Array
|
36
35
|
@additions << [zone, command, value, create_range_commands(zone, command, value)]
|
37
|
-
|
36
|
+
when /^(B|T){xx}$/
|
38
37
|
@additions << [zone, command, value, create_treble_bass_commands(zone, command, value)]
|
39
|
-
|
38
|
+
when /^{xx}$/
|
40
39
|
@additions << [zone, command, value, create_balance_commands(zone, command, value)]
|
41
40
|
else
|
42
41
|
next
|
data/lib/eiscp/message.rb
CHANGED
@@ -15,6 +15,7 @@ module EISCP
|
|
15
15
|
class Message
|
16
16
|
# EISCP header
|
17
17
|
attr_accessor :header
|
18
|
+
|
18
19
|
# ISCP "magic" indicates the start of an eISCP message.
|
19
20
|
MAGIC = 'ISCP'
|
20
21
|
# eISCP header size, fixed length.
|
@@ -53,13 +54,11 @@ module EISCP
|
|
53
54
|
# @param [String] value variable length ISCP command value
|
54
55
|
# @param [String] unit_type_character override default unit type character, optional
|
55
56
|
# @param [String] start_character override default start character, optional
|
56
|
-
def initialize(command: nil, value:
|
57
|
+
def initialize(command: nil, value: '', terminator: "\r\n", unit_type: '1', start: '!')
|
57
58
|
unless Dictionary.known_command?(command)
|
58
59
|
# STDERR.puts "Unknown command #{command}"
|
59
60
|
end
|
60
61
|
|
61
|
-
raise 'No value specified.' if value.nil?
|
62
|
-
|
63
62
|
@command = command
|
64
63
|
@value = value
|
65
64
|
@terminator = terminator
|
@@ -17,14 +17,18 @@ module EISCP
|
|
17
17
|
unit_type: msg.unit_type,
|
18
18
|
start: msg.start
|
19
19
|
)
|
20
|
-
packet.header =
|
20
|
+
packet.header = create_header(array)
|
21
|
+
packet
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.create_header(array)
|
25
|
+
{
|
21
26
|
magic: array[0],
|
22
27
|
header_size: array[1],
|
23
28
|
data_size: array[2],
|
24
29
|
version: array[3],
|
25
30
|
reserved: array[4]
|
26
31
|
}
|
27
|
-
packet
|
28
32
|
end
|
29
33
|
|
30
34
|
def self.validate(packet)
|
@@ -6,7 +6,7 @@ module EISCP
|
|
6
6
|
#
|
7
7
|
module ISCPParser
|
8
8
|
# Regexp for parsing ISCP messages
|
9
|
-
REGEX = /(?<start>!)?(?<unit_type>(
|
9
|
+
REGEX = /(?<start>!)?(?<unit_type>(?:\d|x))?(?<command>[A-Z]{3})\s?(?<value>.*?)(?<terminator>[[:cntrl:]]*$)/
|
10
10
|
def self.parse(string)
|
11
11
|
match = string.match(REGEX)
|
12
12
|
|
@@ -17,9 +17,7 @@ module EISCP
|
|
17
17
|
hash.delete_if { |_, v| v.nil? || v == '' }
|
18
18
|
|
19
19
|
# Convert keys to symbols
|
20
|
-
hash = hash.
|
21
|
-
memo[k.to_sym] = v
|
22
|
-
end
|
20
|
+
hash = hash.transform_keys(&:to_sym)
|
23
21
|
|
24
22
|
Message.new(**hash)
|
25
23
|
end
|
@@ -16,7 +16,7 @@ module EISCP
|
|
16
16
|
Dictionary.commands[zone].each do |command, _values|
|
17
17
|
command_name = Dictionary.command_to_name(command).to_s.gsub(/-/, '_')
|
18
18
|
define_method(command_name) do |v|
|
19
|
-
instance_exec Parser.parse(command_name.gsub(/_/, '-')
|
19
|
+
instance_exec Parser.parse("#{command_name.gsub(/_/, '-')} #{v}"), &block
|
20
20
|
end
|
21
21
|
rescue StandardError => e
|
22
22
|
puts e
|
@@ -28,21 +28,23 @@ module EISCP
|
|
28
28
|
# Returns an array of discovered Receiver objects.
|
29
29
|
#
|
30
30
|
def discover(discovery_port = Receiver::ONKYO_PORT)
|
31
|
-
sock = UDPSocket.new
|
32
|
-
sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_BROADCAST, true)
|
33
|
-
sock.send(ONKYO_MAGIC, 0, '<broadcast>', discovery_port)
|
34
31
|
data = []
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
32
|
+
Socket.ip_address_list.each do | addr |
|
33
|
+
if addr.ipv4?
|
34
|
+
sock = UDPSocket.new
|
35
|
+
sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_BROADCAST, true)
|
36
|
+
sock.bind(addr.ip_address, discovery_port)
|
37
|
+
sock.send(ONKYO_MAGIC, 0, '<broadcast>', discovery_port)
|
38
|
+
loop do
|
39
|
+
msg, addr = sock.recvfrom_nonblock(1024)
|
40
|
+
data << Receiver.new(addr[2], ecn_string_to_ecn_array(msg))
|
41
|
+
rescue IO::WaitReadable
|
42
|
+
io = IO.select([sock], nil, nil, 0.5)
|
43
|
+
break if io.nil?
|
44
|
+
end
|
44
45
|
end
|
45
46
|
end
|
47
|
+
return data
|
46
48
|
end
|
47
49
|
end
|
48
50
|
end
|
data/lib/eiscp/receiver.rb
CHANGED
@@ -133,9 +133,11 @@ module EISCP
|
|
133
133
|
# Sends an EISCP::Message object or string on the network
|
134
134
|
#
|
135
135
|
def send(eiscp)
|
136
|
-
if
|
136
|
+
connect if @socket.nil? || @socket.closed?
|
137
|
+
case eiscp
|
138
|
+
when EISCP::Message
|
137
139
|
@socket.puts(eiscp.to_eiscp)
|
138
|
-
|
140
|
+
when String
|
139
141
|
@socket.puts eiscp
|
140
142
|
end
|
141
143
|
end
|
@@ -143,10 +145,9 @@ module EISCP
|
|
143
145
|
# Reads the socket and returns and EISCP::Message
|
144
146
|
#
|
145
147
|
def recv
|
146
|
-
data =
|
148
|
+
data = String.new
|
147
149
|
data << @socket.gets until data.match(/\r\n$/)
|
148
|
-
|
149
|
-
message
|
150
|
+
Parser.parse(data)
|
150
151
|
end
|
151
152
|
|
152
153
|
# Sends an EISCP::Message object or string on the network and returns recieved data string.
|
@@ -171,7 +172,7 @@ module EISCP
|
|
171
172
|
#
|
172
173
|
def human_readable_state
|
173
174
|
hash = {}
|
174
|
-
@state.each do |c, v|
|
175
|
+
@state.dup.each do |c, v|
|
175
176
|
hash[Dictionary.command_to_name(c).to_s] = (Dictionary.command_value_to_value_name(c, v) || v.to_s).to_s
|
176
177
|
end
|
177
178
|
hash
|
@@ -187,13 +188,7 @@ module EISCP
|
|
187
188
|
info[:values].each do |value, _|
|
188
189
|
next unless value == 'QSTN'
|
189
190
|
|
190
|
-
|
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
|
191
|
+
send_recv(Parser.parse("#{command}QSTN"))
|
197
192
|
end
|
198
193
|
end
|
199
194
|
end
|
data/lib/eiscp.rb
CHANGED
data/test/tc_dictionary.rb
CHANGED
data/test/tc_message.rb
CHANGED
@@ -8,7 +8,10 @@ class TestMessage < MiniTest::Test
|
|
8
8
|
DISCOVERY_STRING = DISCOVERY_PACKET.to_eiscp
|
9
9
|
|
10
10
|
def test_create_discovery_iscp_message
|
11
|
-
assert_equal(
|
11
|
+
assert_equal(
|
12
|
+
EISCP::Message.new(command: 'ECN', value: 'QSTN', terminator: "\r\n", unit_type: 'x',
|
13
|
+
start: '!').to_iscp, '!xECNQSTN'
|
14
|
+
)
|
12
15
|
end
|
13
16
|
|
14
17
|
def test_create_messages
|
data/test/tc_parser.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.1.
|
4
|
+
version: 2.1.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Michael Rodrigues
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-11-18 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 "
|