onkyo_eiscp_ruby 0.0.3 → 2.1.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/README.md +182 -32
- data/VERSION +1 -1
- data/bin/mock_receiver.rb +25 -0
- data/bin/onkyo.rb +85 -25
- data/bin/{onkyo-server.rb → onkyo_server.rb} +2 -1
- data/eiscp-commands.yaml +3911 -3900
- data/lib/eiscp.rb +8 -5
- data/lib/eiscp/dictionary.rb +54 -0
- data/lib/eiscp/dictionary/dictionary_generators.rb +63 -0
- data/lib/eiscp/dictionary/dictionary_helpers.rb +121 -0
- data/lib/eiscp/message.rb +98 -74
- data/lib/eiscp/parser.rb +24 -0
- data/lib/eiscp/parser/dynamic_value_parser.rb +9 -0
- data/lib/eiscp/parser/eiscp_parser.rb +37 -0
- data/lib/eiscp/parser/human_readable_parser.rb +29 -0
- data/lib/eiscp/parser/iscp_parser.rb +28 -0
- data/lib/eiscp/receiver.rb +158 -135
- data/lib/eiscp/receiver/command_methods.rb +28 -0
- data/lib/eiscp/receiver/discovery.rb +49 -0
- data/onkyo_eiscp_ruby.gemspec +15 -13
- data/test/tc_dictionary.rb +45 -0
- data/test/tc_message.rb +11 -12
- data/test/tc_parser.rb +34 -0
- data/test/tc_receiver.rb +4 -3
- metadata +23 -12
- data/lib/eiscp/command.rb +0 -99
- data/lib/eiscp/mock_receiver.rb +0 -22
- data/test/tc_command.rb +0 -43
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../parser'
|
4
|
+
require_relative '../dictionary'
|
5
|
+
|
6
|
+
module EISCP
|
7
|
+
class Receiver
|
8
|
+
# Iterates through every available command and defines a method to call that
|
9
|
+
# command. It's intended to be used through Receiver and uses methods included
|
10
|
+
# by Receiver::Connection. Each method accepts a string that should match the
|
11
|
+
# human readable name of a valid value for that command.
|
12
|
+
#
|
13
|
+
module CommandMethods
|
14
|
+
def self.generate(&block)
|
15
|
+
Dictionary.zones.each do |zone|
|
16
|
+
Dictionary.commands[zone].each do |command, _values|
|
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
|
20
|
+
end
|
21
|
+
rescue StandardError => e
|
22
|
+
puts e
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'socket'
|
4
|
+
require_relative '../message'
|
5
|
+
require_relative '../parser'
|
6
|
+
|
7
|
+
module EISCP
|
8
|
+
class Receiver
|
9
|
+
# This module discovers receivers on the local LAN.
|
10
|
+
#
|
11
|
+
module Discovery
|
12
|
+
# ISCP Magic Packet for Autodiscovery
|
13
|
+
ONKYO_MAGIC = Message.new(command: 'ECN', value: 'QSTN', terminator: "\r\n", unit_type: 'x').to_eiscp
|
14
|
+
|
15
|
+
# Populates Receiver attributes with info from ECNQSTN response.
|
16
|
+
#
|
17
|
+
def ecn_string_to_ecn_array(ecn_string)
|
18
|
+
hash = {}
|
19
|
+
message = Parser.parse(ecn_string)
|
20
|
+
array = message.value.split('/')
|
21
|
+
hash[:model] = array.shift
|
22
|
+
hash[:port] = array.shift.to_i
|
23
|
+
hash[:area] = array.shift
|
24
|
+
hash[:mac_address] = array.shift
|
25
|
+
hash
|
26
|
+
end
|
27
|
+
|
28
|
+
# Returns an array of discovered Receiver objects.
|
29
|
+
#
|
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
|
+
data = []
|
35
|
+
loop do
|
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
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
data/onkyo_eiscp_ruby.gemspec
CHANGED
@@ -1,27 +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
|
6
|
+
s.licenses = ['MIT']
|
4
7
|
s.platform = Gem::Platform::RUBY
|
5
8
|
s.summary = 'Manipulate Onkyo stereos with the eISCP protocol'
|
6
9
|
s.files = Dir.glob('{bin,config,lib,test,doc}/**/*') +
|
7
|
-
|
8
|
-
s.extra_rdoc_files = [
|
10
|
+
['VERSION', 'onkyo_eiscp_ruby.gemspec', 'eiscp-commands.yaml']
|
11
|
+
s.extra_rdoc_files = ['README.md']
|
9
12
|
s.require_path = 'lib'
|
10
13
|
|
11
|
-
s.homepage =
|
14
|
+
s.homepage = 'https://github.com/mikerodrigues/onkyo_eiscp_ruby'
|
12
15
|
|
13
|
-
s.description =
|
14
|
-
Control Onkyo receivers over the network.Use the provided binary
|
16
|
+
s.description = '
|
17
|
+
Control Onkyo receivers over the network.Use the provided binary or
|
15
18
|
require the library for use in your scripts.
|
16
|
-
|
19
|
+
'
|
17
20
|
|
18
|
-
s.author =
|
19
|
-
s.email =
|
21
|
+
s.author = 'Michael Rodrigues'
|
22
|
+
s.email = 'mikebrodrigues@gmail.com'
|
20
23
|
|
21
|
-
s.test_files = Dir[
|
22
|
-
s.executables = %w
|
24
|
+
s.test_files = Dir['test/tc*.rb']
|
25
|
+
s.executables = %w[
|
23
26
|
onkyo.rb
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
+
onkyo_server.rb
|
28
|
+
]
|
27
29
|
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../lib/eiscp/dictionary.rb'
|
4
|
+
require 'minitest/autorun'
|
5
|
+
|
6
|
+
class TestDictionary < MiniTest::Test
|
7
|
+
def test_zone_from_command
|
8
|
+
assert_equal(EISCP::Dictionary.zone_from_command('PWR'), 'main')
|
9
|
+
assert_equal(EISCP::Dictionary.zone_from_command('ZPW'), 'zone2')
|
10
|
+
assert_equal(EISCP::Dictionary.zone_from_command('CDS'), 'dock')
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_command_to_name
|
14
|
+
assert_equal(EISCP::Dictionary.command_to_name('PWR'), 'system-power')
|
15
|
+
assert_equal(EISCP::Dictionary.command_to_name('ZPW'), 'power2')
|
16
|
+
assert_equal(EISCP::Dictionary.command_to_name('PW3'), 'power3')
|
17
|
+
assert_equal(EISCP::Dictionary.command_to_name('PW4'), 'power4')
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_command_name_to_command
|
21
|
+
assert_equal(EISCP::Dictionary.command_name_to_command('system-power'), 'PWR')
|
22
|
+
assert_equal(EISCP::Dictionary.command_name_to_command('master-volume'), 'MVL')
|
23
|
+
assert_equal(EISCP::Dictionary.command_name_to_command('power2'), 'ZPW')
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_command_value_to_value_name
|
27
|
+
assert_equal(EISCP::Dictionary.command_value_to_value_name('PWR', '01'), 'on')
|
28
|
+
assert_equal(EISCP::Dictionary.command_value_to_value_name('PWR', 'QSTN'), 'query')
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_command_value_name_to_value
|
32
|
+
assert_equal(EISCP::Dictionary.command_value_name_to_value('PWR', 'on'), '01')
|
33
|
+
assert_equal(EISCP::Dictionary.command_value_name_to_value('ZPW', 'on'), '01')
|
34
|
+
end
|
35
|
+
|
36
|
+
def test_description_from_command_name
|
37
|
+
assert_equal(EISCP::Dictionary.description_from_command_name('system-power', 'main'), 'System Power Command')
|
38
|
+
assert_equal(EISCP::Dictionary.description_from_command_name('power2', 'zone2'), 'Zone2 Power Command')
|
39
|
+
end
|
40
|
+
|
41
|
+
def test_description_from_command
|
42
|
+
assert_equal(EISCP::Dictionary.description_from_command('PWR'), 'System Power Command')
|
43
|
+
assert_equal(EISCP::Dictionary.description_from_command('ZPW'), 'Zone2 Power Command')
|
44
|
+
end
|
45
|
+
end
|
data/test/tc_message.rb
CHANGED
@@ -1,27 +1,26 @@
|
|
1
|
-
|
2
|
-
require "test/unit"
|
1
|
+
# frozen_string_literal: true
|
3
2
|
|
4
|
-
|
3
|
+
require_relative '../lib/eiscp/message'
|
4
|
+
require 'minitest/autorun'
|
5
5
|
|
6
|
-
|
7
|
-
DISCOVERY_PACKET = EISCP::Message.new('ECN', 'QSTN', 'x', '!')
|
6
|
+
class TestMessage < MiniTest::Test
|
7
|
+
DISCOVERY_PACKET = EISCP::Message.new(command: 'ECN', value: 'QSTN', terminator: "\r\n", unit_type: 'x', start: '!')
|
8
8
|
DISCOVERY_STRING = DISCOVERY_PACKET.to_eiscp
|
9
9
|
|
10
|
-
|
11
10
|
def test_create_discovery_iscp_message
|
12
|
-
assert_equal(EISCP::Message.new(
|
11
|
+
assert_equal(EISCP::Message.new(command: 'ECN', value: 'QSTN', terminator: "\r\n", unit_type: 'x', start: '!').to_iscp, '!xECNQSTN')
|
13
12
|
end
|
14
13
|
|
15
|
-
def
|
16
|
-
assert_equal(EISCP::Message.
|
14
|
+
def test_create_messages
|
15
|
+
assert_equal(EISCP::Message.new(command: 'PWR', value: '01').to_iscp, '!1PWR01')
|
16
|
+
assert_equal(EISCP::Message.new(command: 'MVL', value: 'QSTN').to_iscp, '!1MVLQSTN')
|
17
17
|
end
|
18
18
|
|
19
19
|
def test_create_discovery_packet_string
|
20
20
|
assert_equal(DISCOVERY_PACKET.to_eiscp, DISCOVERY_STRING)
|
21
21
|
end
|
22
22
|
|
23
|
-
def
|
24
|
-
|
23
|
+
def test_validate_valid_message_with_variable
|
24
|
+
# Commands that return something unexpected like an artist name
|
25
25
|
end
|
26
|
-
|
27
26
|
end
|
data/test/tc_parser.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../lib/eiscp/parser'
|
4
|
+
require_relative '../lib/eiscp/message'
|
5
|
+
require 'minitest/autorun'
|
6
|
+
|
7
|
+
class TestParser < MiniTest::Test
|
8
|
+
DISCOVERY_PACKET = EISCP::Message.new(command: 'ECN', value: 'QSTN', terminator: "\r\n", unit_type: 'x', start: '!')
|
9
|
+
DISCOVERY_STRING = DISCOVERY_PACKET.to_eiscp
|
10
|
+
|
11
|
+
def test_parse_discovery_iscp_message
|
12
|
+
assert_equal(EISCP::Parser.parse('!xECNQSTN').to_iscp, '!xECNQSTN')
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_parse_iscp_messages
|
16
|
+
assert_equal(EISCP::Parser.parse('PWR 01').to_iscp, '!1PWR01')
|
17
|
+
assert_equal(EISCP::Parser.parse('PWR01').to_iscp, '!1PWR01')
|
18
|
+
assert_equal(EISCP::Parser.parse('!1PWR01').to_iscp, '!1PWR01')
|
19
|
+
assert_equal(EISCP::Parser.parse('!1PWR 01').to_iscp, '!1PWR01')
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_parse_discovery_packet_string
|
23
|
+
assert_equal(EISCP::Parser.parse(DISCOVERY_STRING).to_eiscp, DISCOVERY_PACKET.to_eiscp)
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_parse_human_readable
|
27
|
+
assert_equal(EISCP::Parser.parse('system-power on'), EISCP::Message.new(command: 'PWR', value: '01'))
|
28
|
+
assert_equal(EISCP::Parser.parse('main system-power on'), EISCP::Message.new(command: 'PWR', value: '01'))
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_return_nil_for_fake_human_readable
|
32
|
+
assert_equal(EISCP::Parser.parse('fake-command value'), nil)
|
33
|
+
end
|
34
|
+
end
|
data/test/tc_receiver.rb
CHANGED
metadata
CHANGED
@@ -1,41 +1,52 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: onkyo_eiscp_ruby
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.1.2
|
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 "
|
15
15
|
email: mikebrodrigues@gmail.com
|
16
16
|
executables:
|
17
17
|
- onkyo.rb
|
18
|
-
-
|
18
|
+
- onkyo_server.rb
|
19
19
|
extensions: []
|
20
20
|
extra_rdoc_files:
|
21
21
|
- README.md
|
22
22
|
files:
|
23
23
|
- README.md
|
24
24
|
- VERSION
|
25
|
-
- bin/
|
25
|
+
- bin/mock_receiver.rb
|
26
26
|
- bin/onkyo.rb
|
27
|
+
- bin/onkyo_server.rb
|
27
28
|
- eiscp-commands.yaml
|
28
29
|
- lib/eiscp.rb
|
29
|
-
- lib/eiscp/
|
30
|
+
- lib/eiscp/dictionary.rb
|
31
|
+
- lib/eiscp/dictionary/dictionary_generators.rb
|
32
|
+
- lib/eiscp/dictionary/dictionary_helpers.rb
|
30
33
|
- lib/eiscp/message.rb
|
31
|
-
- lib/eiscp/
|
34
|
+
- lib/eiscp/parser.rb
|
35
|
+
- lib/eiscp/parser/dynamic_value_parser.rb
|
36
|
+
- lib/eiscp/parser/eiscp_parser.rb
|
37
|
+
- lib/eiscp/parser/human_readable_parser.rb
|
38
|
+
- lib/eiscp/parser/iscp_parser.rb
|
32
39
|
- lib/eiscp/receiver.rb
|
40
|
+
- lib/eiscp/receiver/command_methods.rb
|
41
|
+
- lib/eiscp/receiver/discovery.rb
|
33
42
|
- onkyo_eiscp_ruby.gemspec
|
34
|
-
- test/
|
43
|
+
- test/tc_dictionary.rb
|
35
44
|
- test/tc_message.rb
|
45
|
+
- test/tc_parser.rb
|
36
46
|
- test/tc_receiver.rb
|
37
47
|
homepage: https://github.com/mikerodrigues/onkyo_eiscp_ruby
|
38
|
-
licenses:
|
48
|
+
licenses:
|
49
|
+
- MIT
|
39
50
|
metadata: {}
|
40
51
|
post_install_message:
|
41
52
|
rdoc_options: []
|
@@ -52,12 +63,12 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
52
63
|
- !ruby/object:Gem::Version
|
53
64
|
version: '0'
|
54
65
|
requirements: []
|
55
|
-
|
56
|
-
rubygems_version: 2.2.0
|
66
|
+
rubygems_version: 3.2.3
|
57
67
|
signing_key:
|
58
68
|
specification_version: 4
|
59
69
|
summary: Manipulate Onkyo stereos with the eISCP protocol
|
60
70
|
test_files:
|
71
|
+
- test/tc_dictionary.rb
|
61
72
|
- test/tc_message.rb
|
73
|
+
- test/tc_parser.rb
|
62
74
|
- test/tc_receiver.rb
|
63
|
-
- test/tc_command.rb
|
data/lib/eiscp/command.rb
DELETED
@@ -1,99 +0,0 @@
|
|
1
|
-
require 'yaml'
|
2
|
-
require 'eiscp/receiver'
|
3
|
-
require 'ostruct'
|
4
|
-
|
5
|
-
module Command
|
6
|
-
|
7
|
-
@@yaml_file_path = File.join(File.expand_path(File.dirname(__FILE__)), '../../eiscp-commands.yaml')
|
8
|
-
@@yaml_object = YAML.load(File.read(@@yaml_file_path))
|
9
|
-
@@modelsets = @@yaml_object["modelsets"]
|
10
|
-
@@yaml_object.delete("modelsets")
|
11
|
-
@@zones = @@yaml_object.map{|k, v| k}
|
12
|
-
@@zones.each {|zone| class_variable_set("@@#{zone}", nil) }
|
13
|
-
@@main = @@yaml_object['main']
|
14
|
-
|
15
|
-
def self.command_to_name(command)
|
16
|
-
return @@main[command]['name']
|
17
|
-
end
|
18
|
-
|
19
|
-
def self.command_name_to_command(name)
|
20
|
-
@@main.each_pair do |command, attrs|
|
21
|
-
if attrs['name'] == name
|
22
|
-
return command
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
def self.command_value_to_value_name(command, value)
|
28
|
-
return @@main[command]['values'][value]['name']
|
29
|
-
end
|
30
|
-
|
31
|
-
def self.command_value_name_to_value(command, name)
|
32
|
-
@@main[command]['values'].each do |k, v|
|
33
|
-
if v['name'] == name.to_s
|
34
|
-
return k
|
35
|
-
end
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
|
40
|
-
def self.description_from_command_name(name)
|
41
|
-
@@main.each_pair do |command, attrs|
|
42
|
-
if attrs['name'] == name
|
43
|
-
return @@main[command]['description']
|
44
|
-
end
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
|
-
def self.description_from_command(command)
|
49
|
-
return @@main[command]['description']
|
50
|
-
end
|
51
|
-
|
52
|
-
def self.description_from_command_value(command, value)
|
53
|
-
return @@main[command]['values'].select do |k, v|
|
54
|
-
if k == value
|
55
|
-
return v['description']
|
56
|
-
end
|
57
|
-
end
|
58
|
-
end
|
59
|
-
|
60
|
-
def self.list_all_commands
|
61
|
-
@@main.each_pair do |command, attrs|
|
62
|
-
puts "#{command} - #{attrs['name']}: #{attrs['description']}"
|
63
|
-
attrs['values'].each_pair do |k, v|
|
64
|
-
puts "--#{k}:#{v}"
|
65
|
-
end
|
66
|
-
end
|
67
|
-
end
|
68
|
-
|
69
|
-
def self.list_compatible_commands(modelstring)
|
70
|
-
sets = []
|
71
|
-
@@modelsets.each_pair do |set, array|
|
72
|
-
if array.include? modelstring
|
73
|
-
sets << set
|
74
|
-
end
|
75
|
-
end
|
76
|
-
return sets
|
77
|
-
end
|
78
|
-
|
79
|
-
def self.parse(string)
|
80
|
-
array = string.split(" ")
|
81
|
-
zone = 'main'
|
82
|
-
command_name = ''
|
83
|
-
parameter_name = ''
|
84
|
-
if array.count == 3
|
85
|
-
zone = array.shift
|
86
|
-
command_name = array.shift
|
87
|
-
parameter_name = array.shift
|
88
|
-
elsif array.count == 2
|
89
|
-
command_name = array.shift
|
90
|
-
parameter_name = array.shift
|
91
|
-
end
|
92
|
-
command = Command.command_name_to_command(command_name)
|
93
|
-
parameter = Command.command_value_name_to_value(command, parameter_name)
|
94
|
-
return EISCP::Message.new(command, parameter)
|
95
|
-
end
|
96
|
-
|
97
|
-
end
|
98
|
-
|
99
|
-
|
data/lib/eiscp/mock_receiver.rb
DELETED
@@ -1,22 +0,0 @@
|
|
1
|
-
require 'socket'
|
2
|
-
require 'eiscp/receiver'
|
3
|
-
require 'eiscp/message'
|
4
|
-
|
5
|
-
# Mock server that only responds to ECNQSTN.
|
6
|
-
|
7
|
-
module EISCP
|
8
|
-
class MockReceiver
|
9
|
-
|
10
|
-
ONKYO_DISCOVERY_RESPONSE = Message.new("ECN", "TX-NR609/60128/DX/001122334455")
|
11
|
-
|
12
|
-
# Create/start the server object.
|
13
|
-
|
14
|
-
def initialize
|
15
|
-
Socket.udp_server_loop("255.255.255.255", EISCP::ONKYO_PORT) do |msg, msg_src|
|
16
|
-
msg_src.reply ONKYO_DISCOVERY_RESPONSE.to_eiscp
|
17
|
-
puts msg
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
|
-
end
|
22
|
-
end
|