onkyo_eiscp_ruby 2.1.1 → 2.1.6
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/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
|
[](http://badge.fury.io/rb/onkyo_eiscp_ruby)
|
4
4
|
[](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 "
|